Issue with margins around a map in geopandas - matplotlib

I cannot seem to solve some margin/location issue between the GeoPandas map and added annotations. Annotations are added by functions addChartSignature and addTitle and they cause a terrible layout.
I have managed to sort some vertical margins issue with a hack, see comment ### to deal with vertical margin issue, however I cannot seem to deal with the margin on the left side: I would like the map, the signature and the title to be aligned with the left side with a very small margin (like on the right side).
I added the variable righthspace to move annotations right, but it doesn't help and it doesn't feel right anyway.
Below a code sample which summarize the issue
from __future__ import division
import matplotlib.pyplot as plt
import geopandas
from mpl_toolkits.axes_grid1 import make_axes_locatable
def addChartSignature(ax, vspace=0, righthspace=0):
ax.annotate('',
xy=(0.97, 0.05 + vspace),
xycoords='figure fraction',
xytext=(0.03 + righthspace,0.05 + vspace),
textcoords='figure fraction',
arrowprops=dict(arrowstyle="-",
linewidth=0.7,
facecolor='grey',
alpha=.7,
edgecolor='grey'),
horizontalalignment = 'center',
verticalalignment='bottom')
ax.annotate(u" ©myCompany",
xy=(0.5, 0.5),
xycoords='figure fraction',
xytext= (0.03 + righthspace,0.01+vspace),
textcoords='figure fraction',
ha="left",
va="bottom",
color = 'grey',
alpha = .7,
fontsize = 11)
ax.annotate(u"Source: Internal",
xy=(0.5, 0.5),
xycoords='figure fraction',
xytext=(0.97,0.01+vspace),
textcoords='figure fraction',
ha="right",
va="bottom",
color = 'grey',
alpha = .7,
fontsize = 11)
def addTitle(ax, vspace=0, righthspace=0):
ax.annotate("My Chart Title",
xy=(0.5, 0.5),
xycoords='figure fraction',
xytext=(0.01+righthspace, 0.985+vspace),
textcoords='figure fraction',
ha="left",
va="top",
color = 'black',
alpha = .75,
fontsize = 19,
weight = 'bold')
path = geopandas.datasets.get_path('naturalearth_lowres')
world = geopandas.read_file(path)
world = world[(world.pop_est>0) & (world.name!="Antarctica")]
plt.style.use('fivethirtyeight')
fig = plt.figure(figsize=(8, 6))
ax = fig.add_axes([0., 0., 1, 1])
ax = world.plot(color="lightgrey", ax=ax)
divider = make_axes_locatable(ax)
cax = divider.append_axes('right', size="3%", pad=-1.3)
world.dropna().plot(
column='pop_est',
ax=ax,
legend=True,
cax=cax,
cmap='RdYlGn',
)
ax.grid(color='#F8F8F8')
ax.set_xticklabels([])
ax.set_yticklabels([])
### to deal with vertical margin issue
ax.set_aspect(aspect=4./3)
ax.margins(0)
ax.apply_aspect()
bbox = ax.get_window_extent().inverse_transformed(fig.transFigure)
w,h = fig.get_size_inches()
fig.set_size_inches(w*bbox.width, h*bbox.height)
addChartSignature(ax, righthspace = 0.05)
addTitle(ax, righthspace = 0.05)

Related

Creating labelled horizontal lines on a plot

I'm trying to reproduce this diagram:
but I'm having trouble creating the horizontal lines with bars. I've tried annotate and hlines but they don't quite give the effect I'm after.
import matplotlib.pyplot as plt
plt.grid(which = 'both')
plt.xticks(fontsize = 16)
plt.yticks(fontsize = 16)
plt.xlim(-0.5,8)
plt.ylim(-0.5,10)
plt.xlabel('Redshift, z', fontsize = 16)
plt.hlines(8, 0, .3)
plt.annotate(r'H$\alpha$', fontsize = 16, xy = (0,8), xycoords='data', xytext=(0,8),
textcoords='data',
arrowprops=dict(arrowstyle='<|-|>', connectionstyle='arc3', color = 'k', lw=2))
fig = plt.gcf()
width, height = 15,35 # inches
fig.set_size_inches(width, height, forward = True)
plt.show()
What's the best way to produce the bars like this?
I would use annotate directly, but for more flexibility, I would separate the drawing of the horizontal bars and the corresponding text
plt.figure()
plt.grid(which = 'both')
plt.xticks(fontsize = 16)
plt.yticks(fontsize = 16)
plt.xlim(-0.5,8)
plt.ylim(-0.5,10)
plt.xlabel('Redshift, z', fontsize = 16)
bar_ys = [8,4]
bar_xs = [[0,6],[3,5]]
bar_texts = [r'H$\alpha$',r'H$\beta$']
bar_color = ['k','orange']
for y,xs,t,c in zip(bar_ys,bar_xs,bar_texts,bar_color):
plt.annotate('', xy = (xs[0],y), xycoords='data', xytext=(xs[1],y),
arrowprops=dict(arrowstyle='|-|', color=c, lw=2, shrinkA=0, shrinkB=0))
plt.annotate(t, xy = (xs[1],y), xycoords='data', xytext=(-5,5), textcoords='offset points',
fontsize = 16, va='baseline', ha='right', color=c)
plt.show()
The accepted answer works perfectly, thank you.
In addition, I automated the colours thus:
colors = iter(cm.tab10(np.linspace(0,0.8,13)))
colour = 'k'
for y,xs,t in zip(bar_ys,bar_xs,bar_texts):
plt.annotate('', xy = (xs[0],y), xycoords='data', xytext=(xs[1],y),
arrowprops=dict(arrowstyle='|-|', color=colour, lw=2, shrinkA=0, shrinkB=0))
plt.annotate(t, xy = (xs[1],y), xycoords='data', xytext=(-5,5), textcoords='offset points',
fontsize = 16, va='baseline', ha='right', color=colour)
colour = next(colors)

How to set the updated value of Slider always in the centre?

Following the Slider Demo of Matplotlib https://matplotlib.org/gallery/widgets/slider_demo.html, I would like to update the Slider ranges, so that every time I change the slider values, those are re-centred in the Slider.
I have tried to define the Sliders as
sfreq = Slider(axfreq, 'Freq', freq-10, freq+10, valinit=freq)
samp = Slider(axamp, 'Amp', amp-5, amp+5, valinit=amp)
but since the update() function does not return anything, that does not work. I also tried making these variables global inside the function, which also did not work. I finally tried defining the Sliders inside the update function,
def update(val):
amp = samp.val
freq = sfreq.val
l.set_ydata(amp*np.sin(2*np.pi*freq*t))
fig.canvas.draw_idle()
Slider(axfreq, 'Freq', freq-10, freq+10, valinit=freq)
Slider(axamp, 'Amp', amp-5, amp+5, valinit=amp)
but that overlays more and more Sliders as I change the values. Any suggestions?
So I just decided to make the range of the slider cover several orders of magnitude of the parameter, and display the values in a logarithmic scale. In case anyone wonders, and following the matplotlib demo:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 10
delta_f = 5.0
s = a0*np.sin(2*np.pi*f0*t)
l, = plt.plot(t, s, lw=2, color='red')
plt.axis([0, 1, -10, 10])
axcolor = 'lightgoldenrodyellow'
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor)
sfreq = Slider(axfreq, 'Freq', np.log(1), np.log10(1000), valinit=np.log10(f0), valfmt='%4.2E')
samp = Slider(axamp, 'Amp', a0-5, a0+5, valinit=a0)
def update(val):
amp = samp.val
freq = sfreq.val
sfreq.valtext.set_text('{:4.2E}'.format(10**freq))
l.set_ydata(amp*np.sin(2*np.pi*10**freq*t))
fig.canvas.draw_idle()
sfreq.on_changed(update)
samp.on_changed(update)
resetax = plt.axes([0.8, 0.025, 0.1, 0.04] )
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
def reset(event):
sfreq.reset()
samp.reset()
button.on_clicked(reset)
rax = plt.axes([0.025, 0.5, 0.15, 0.15], facecolor=axcolor)
radio = RadioButtons(rax, ('red', 'blue', 'green'), active=0)
def colorfunc(label):
l.set_color(label)
fig.canvas.draw_idle()
radio.on_clicked(colorfunc)
plt.show()

Adding edges to bars in matplotlib

I have been following the example provided in:
https://matplotlib.org/examples/api/barchart_demo.html
My problem is that I want to add edges to the bars. But when I set the
linewidth=1, edgecolor='black'
parameters, the edges are only applied to the first pair of bars, leaving the remaining pairs unchanged.
"""
========
Barchart
========
A bar plot with errorbars and height labels on individual bars
"""
import numpy as np
import matplotlib.pyplot as plt
N = 5
men_means = (20, 35, 30, 35, 27)
men_std = (2, 3, 4, 1, 2)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
fig, ax = plt.subplots()
rects1 = ax.bar(ind, men_means, width, color='r', yerr=men_std,linewidth=1, edgecolor='black')
women_means = (25, 32, 34, 20, 25)
women_std = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind + width, women_means, width, color='y', yerr=women_std, linewidth=1, edgecolor='black')
# add some text for labels, title and axes ticks
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width / 2)
ax.set_xticklabels(('G1', 'G2', 'G3', 'G4', 'G5'))
ax.legend((rects1[0], rects2[0]), ('Men', 'Women'))
def autolabel(rects):
"""
Attach a text label above each bar displaying its height
"""
for rect in rects:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width()/2., 1.05*height,
'%d' % int(height),
ha='center', va='bottom')
autolabel(rects1)
autolabel(rects2)
plt.show()
Thanks for your help.
David.

Adding gridlines using Cartopy

I'm trying to add gridlines to a map I made using Cartopy, however, when I use the example code from the cartopy documentation, it doesn't display what I want and I can't figure out how to manipulate it to do so.
def plotMap():
proj = ccrs.Mercator(central_longitude=180, min_latitude=15,
max_latitude=55)
fig, ax = plt.subplots(subplot_kw=dict(projection=proj), figsize=(12,12))
ax.set_extent([255 ,115, 0, 60], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor='0.3')
ax.add_feature(cfeature.LAKES, alpha=0.9)
ax.add_feature(cfeature.BORDERS, zorder=10)
ax.add_feature(cfeature.COASTLINE, zorder=10)
#(http://www.naturalearthdata.com/features/)
states_provinces = cfeature.NaturalEarthFeature(
category='cultural', name='admin_1_states_provinces_lines',
scale='50m', facecolor='none')
ax.add_feature(states_provinces, edgecolor='black', zorder=10)
#ax.gridlines(xlocs=grids_ma, ylocs=np.arange(-80,90,20), zorder=21,
draw_labels=True )
ax.gridlines(crs=ccrs.PlateCarree(), linewidth=2, color='black',
draw_labels=True, alpha=0.5, linestyle='--')
ax.xlabels_top = False
ax.ylabels_left = False
ax.ylabels_right=True
ax.xlines = True
ax.xlocator = mticker.FixedLocator([-160, -140, -120, 120, 140, 160, 180,])
ax.xformatter = LONGITUDE_FORMATTER
ax.yformatter = LATITUDE_FORMATTER
ax.xlabel_style = {'size': 15, 'color': 'gray'}
ax.xlabel_style = {'color': 'red', 'weight': 'bold'}
return fig, ax
I've attached a picture of the output. For reference, I only want the longitude gridlines to start at the left of my domain and end at the right side, preferably being spaced every 20 degrees. Ideally the same for latitude lines as well.
Bad gridline plot
Is the example you are following the one at the bottom of this page? If so, you are attempting to set attributes on the GeoAxes (ax) instance which should be set on the GridLiner (gl) instance:
import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
def plotMap():
proj = ccrs.Mercator(central_longitude=180, min_latitude=15,
max_latitude=55)
fig, ax = plt.subplots(subplot_kw=dict(projection=proj), figsize=(12,12))
ax.set_extent([255 ,115, 0, 60], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor='0.3')
ax.add_feature(cfeature.LAKES, alpha=0.9)
ax.add_feature(cfeature.BORDERS, zorder=10)
ax.add_feature(cfeature.COASTLINE, zorder=10)
states_provinces = cfeature.NaturalEarthFeature(
category='cultural', name='admin_1_states_provinces_lines',
scale='50m', facecolor='none')
ax.add_feature(states_provinces, edgecolor='black', zorder=10)
gl = ax.gridlines(crs=ccrs.PlateCarree(), linewidth=2, color='black', alpha=0.5, linestyle='--', draw_labels=True)
gl.xlabels_top = False
gl.ylabels_left = False
gl.ylabels_right=True
gl.xlines = True
gl.xlocator = mticker.FixedLocator([120, 140, 160, 180, -160, -140, -120])
gl.ylocator = mticker.FixedLocator([0, 20, 40, 60])
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
gl.xlabel_style = {'color': 'red', 'weight': 'bold'}
This produces the following map. The gridliner doesn't seem to be coping with the dateline. I do not know if there is a way around this, but there is a note at the top of the above linked documentation to say that there are currently known limitations with this class, so maybe not.
An alternative is to set the various labels and their styles directly with matplotlib. Note that you have to set the ticklabels separately from the ticks, otherwise you get labels corresponding to the Mercator coordinate reference system:
import cartopy.mpl.ticker as cticker
def plotMap2():
proj = ccrs.Mercator(central_longitude=180, min_latitude=15,
max_latitude=55)
fig, ax = plt.subplots(subplot_kw=dict(projection=proj), figsize=(12,12))
ax.set_extent([255 ,115, 0, 60], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND, facecolor='0.3')
ax.add_feature(cfeature.LAKES, alpha=0.9)
ax.add_feature(cfeature.BORDERS, zorder=10)
ax.add_feature(cfeature.COASTLINE, zorder=10)
states_provinces = cfeature.NaturalEarthFeature(
category='cultural', name='admin_1_states_provinces_lines',
scale='50m', facecolor='none')
ax.add_feature(states_provinces, edgecolor='black', zorder=10)
ax.set_xticks([120., 140., 160., 180., -160., -140., -120.], crs=ccrs.PlateCarree())
ax.set_xticklabels([120., 140., 160., 180., -160., -140., -120.], color='red', weight='bold')
ax.set_yticks([20, 40], crs=ccrs.PlateCarree())
ax.set_yticklabels([20, 40])
ax.yaxis.tick_right()
lon_formatter = cticker.LongitudeFormatter()
lat_formatter = cticker.LatitudeFormatter()
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)
ax.grid(linewidth=2, color='black', alpha=0.5, linestyle='--')

Compact horizontal guage Matplotlib

How to create a compact horizontal gauge like for example a thermometer for temperature, barometer for pressure using Matplotlib. The scale of the gauge will be split into ranges; each range denoting high-high, high. low and low-low and a pointer reading the value? Is it possible to create such a gauge in matplotlib?
You could use a colorbar.
For example:
import matplotlib.pyplot as plt
import matplotlib as mpl
fig = plt.figure(figsize=(8, 2))
ax = fig.add_axes([0.1, 0.4, 0.8, 0.2])
bounds = [-20, -10, 0, 10, 20]
labels = ('low-low', 'low', 'high', 'high-high')
cmap = mpl.cm.coolwarm
norm = mpl.colors.Normalize(vmin=bounds[0], vmax=bounds[-1])
cb = mpl.colorbar.ColorbarBase(
ax,
cmap=cmap,
norm=norm,
orientation='horizontal',
boundaries=bounds,
label='temperature (degrees celcius)',
)
for i, label in enumerate(labels):
xpos = float((2*i + 1))/(2*len(labels))
ax.annotate(label, xy=(xpos, 0.5), xycoords='axes fraction', ha='center', va='center')
plt.show()
Which produces something like this:
For more info see these examples in the matplotlib docs.