Colormap with colored quiver - python

I am plotting a map with arrows on top of it. These arrows represent winddirections, average windspeed (per direction) and the occurence (per direction).
The direction is indicated by the direction of the arrow. The length of the arrow indicated the average windspeed in that direction. The color of the arrow indicates the occurence of winds in such a direction.
This all works fine with the script below:
windData = pd.read_csv(src+'.txt'), sep='\t', names=['lat', 'lon', 'wind_dir_start', 'wind_dir_end', 'total_num_data_points','num_data_points', 'avg_windspeed']).dropna()
# plot map
m = Basemap(llcrnrlon=minLon, llcrnrlat=minLat, urcrnrlon=maxLon, urcrnrlat=maxLat, resolution='i')
Left, Bottom = m(minLon, minLat)
Right, Top = m(maxLon, maxLat)
# get x y
x, y = m(windData['lon'], windData['lat'])
# angles
angleStart = -windData['wind_start']+90
angleStart[angleStart<0] = np.radians(angleStart[angleStart<0]+360.)
angleEnd = -windData['wind_end']+90
angleEnd[angleEnd<0] = np.radians(angleEnd[angleEnd<0]+360.)
angle = angleStart + math.radians(binSize/2.)
xux = np.cos(angle) * windData['avg_windspeed']
yuy = np.sin(angle) * windData['avg_windspeed']
# occurence
occurence = (windData['num_data_points']/windData['total_num_data_points'])
xi = np.linspace(minLon, maxLon, 300)
yi = np.linspace(minLat, maxLat, 300)
# plotting
## xux and yuy are used negatively because they are measured as "coming from" and displayed as "going to"
# To make things more readable I left a threshold for the occurence out
# I usually plot x, y, xux, yuy and the colors as var[occurence>threshold]
Q = m.quiver(x, y, -xux, -yuy, scale=75, zorder=6, color=cm.jet, width=0.0003*Width, cmap=cm.jet)
qk = plt.quiverkey(Q, 0.5, 0.92, 3, r'$3 \frac{m}{s}$', labelpos='S', fontproperties={'weight': 'bold'})
m.scatter(x, y, c='k', s=20*np.ones(len(x)), zorder=10, vmin=4.5, vmax=39.)
This plot shows the arrows well, but now I want to add a colormap that indicates the percentage of occurence next to the plot. How would I do this?

OK
Usual imports, plus import matplotlib
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
Fake the data to be plotted (tx for the MCVE)
NP = 10
np.random.seed(1)
x = np.random.random(NP)
y = np.random.random(NP)
angle = 1.07+np.random.random(NP) # NE to NW
velocity = 1.50+np.random.random(NP)
o = np.random.random(NP)
occurrence = o/np.sum(o)
dx = np.cos(angle)*velocity
dy = np.sin(angle)*velocity
Create a mappable so that Matplotib has no reason to complain "RuntimeError: No mappable was found to use for colorbar creation."
norm = matplotlib.colors.Normalize()
norm.autoscale(occurrence)
cm = matplotlib.cm.copper
sm = matplotlib.cm.ScalarMappable(cmap=cm, norm=norm)
sm.set_array([])
and plot the data
plt.quiver(x, y, dx, dy, color=cm(norm(o)))
plt.colorbar(sm)
plt.show()
References:
A logarithmic colorbar in matplotlib scatter plot
,
Drawing a colorbar aside a line plot, using Matplotlib
and
Different colours for arrows in quiver plot.
P.S. In recent (for sure in 3.+) Matplotlib releases the cm.set_array incantation is no more necessary

Do you want the colorbar to show the different wind speeds? If so, it might be sufficient to place plt.colorbar() between the lines Q = m.quiver(...) and qk = ....

Related

How to animate multiple dots moving along the circumference of a circle in Python using matplotlib?

I'm trying to animate multiple dots moving along the circumference of their own circle using matplotlib.
I've been able to animate a single dot moving along a circle, and here's the code to do that:
import numpy as np
import argparse
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# To make the waving flag, we need N dots moving on a circle
# Each subsequent dot is going to be delayed by a slight time, and the last dot should be the same timing as the first dot
r = 3
def circle(phi, phi_off,offset_x, offset_y):
return np.array([r*np.cos(phi+phi_off), r*np.sin(phi+phi_off)]) + np.array([offset_x, offset_y])
plt.rcParams["figure.figsize"] = 8,6
# create a figure with an axes
fig, ax = plt.subplots()
# set the axes limits
ax.axis([-30,30,-30,30])
# set equal aspect such that the circle is not shown as ellipse
ax.set_aspect("equal")
# create a point in the axes
point, = ax.plot(0,1, marker="o")
def update(phi, phi_off, offset_x,offset_y):
# obtain point coordinates
x,y = circle(phi,phi_off, offset_x,offset_y)
# set point coordinates
point.set_data([x],[y])
return point,
ani = animation.FuncAnimation(fig,update,fargs=(0,8*i,0, ), interval = 2, frames=np.linspace(0,2*np.pi,360, endpoint=False))
It looks like this :
In order to have multiple dots, I tried to do ani.append in a loop, i.e. have it do something like this:
i=0
for i in range(3):
ani.append(animation.FuncAnimation(fig,update,fargs=(0,8*i,0, ), interval = 2, frames=np.linspace(0,2*np.pi,360, endpoint=False)))
Here's what it looks like:
Any ideas on how to have multiple dots each moving smoothly on their own circle?
You should only define one update function, which is updating all points:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
r = 3
def circle(phi, phi_off,offset_x, offset_y):
return np.array([r*np.cos(phi+phi_off), r*np.sin(phi+phi_off)]) + np.array([offset_x, offset_y])
plt.rcParams["figure.figsize"] = 8,6
fig, ax = plt.subplots()
ax.axis([-30,30,-30,30])
ax.set_aspect("equal")
# create initial conditions
phi_offs = [0, np.pi/2, np.pi]
offset_xs = [0, 0, 0]
offset_ys = [0, 0, 0]
# amount of points
N = len(phi_offs)
# create a point in the axes
points = []
for i in range(N):
x,y = circle(0, phi_offs[i], offset_xs[i], offset_ys[i])
points.append(ax.plot(x, y, marker="o")[0])
def update(phi, phi_off, offset_x,offset_y):
# set point coordinates
for i in range(N):
x, y = circle(phi,phi_off[i], offset_x[i], offset_y[i])
points[i].set_data([x],[y])
return points
ani = animation.FuncAnimation(fig,update,
fargs=(phi_offs, offset_xs, offset_ys),
interval = 2,
frames=np.linspace(0,2*np.pi,360, endpoint=False),
blit=True)
plt.show()
I also added the blit=True argument to make the animation smoother and faster (only the necessary artists will be updated) but be careful, you might have to omit this feature in more complex animations.

Conditionally moving the position of a single data label in a pie chart

The following sample code will generate the donut chart I'll use as my example:
import matplotlib.pyplot as plt
%matplotlib inline
# Following should supposedly set the font correctly:
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Muli'] + plt.rcParams['font.sans-serif']
plt.rcParams['font.weight'] = 'extra bold'
size_of_groups=[12,11,30,0.3]
colors = ['#a1daaa','#bbbbb4','#444511','#1afff2']
import matplotlib as mpl
mpl.rcParams['text.color'] = '#273859'
# Create a pieplot
my_pie,texts,_ = plt.pie(size_of_groups,radius = 1.2,colors=colors,autopct="%.1f%%",
textprops = {'color':'w',
'size':15 #, 'weight':"extra bold"
}, pctdistance=0.75, labeldistance=0.7) #pctdistance and labeldistance change label positions.
labels=['High','Low','Normal','NA']
plt.legend(my_pie,labels,loc='lower center',ncol=2,bbox_to_anchor=(0.5, -0.2))
plt.setp(my_pie, width=0.6, edgecolor='white')
fig1 = plt.gcf()
fig1.show()
The above outputs this:
Mostly, this is great. Finally I got a nice looking donut chart!
But there is just one last thing to finesse - when the portion of the donut chart is very small (like the 0.6%), I need the labels to be moved out of the chart, and possibly colored black instead.
I managed to do something similar for bar charts using plt.text, but I don't think that will be feasible with pie charts at all. I figure someone has definitely solved a similar problem before, but I can't readily fine any decent solutions.
Here is a way to move all percent-texts for patches smaller than some given amount (5 degrees in the code example). Note that this will also fail when there would be multiple small pieces close to each other.
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
size_of_groups = [12, 11, 30, 0.3]
colors = ['#a1daaa', '#bbbbb4', '#444511', '#1afff2']
my_pie, texts, pct_txts = plt.pie(size_of_groups, radius=1.2, colors=colors, autopct="%.1f%%",
textprops={'color': 'w', 'size': 15}, pctdistance=0.75,
labeldistance=0.7)
labels = ['High', 'Low', 'Normal', 'NA']
plt.legend(my_pie, labels, loc='lower center', ncol=2, bbox_to_anchor=(0.5, -0.2))
plt.setp(my_pie, width=0.6, edgecolor='white')
for patch, txt in zip(my_pie, pct_txts):
if (patch.theta2 - patch.theta1) <= 5:
# the angle at which the text is normally located
angle = (patch.theta2 + patch.theta1) / 2.
# new distance to the pie center
x = patch.r * 1.2 * np.cos(angle * np.pi / 180)
y = patch.r * 1.2 * np.sin(angle * np.pi / 180)
# move text to new position
txt.set_position((x, y))
txt.set_color('black')
plt.tight_layout()
plt.show()
I attempted a solution by tweaking the solution of ImportanceOfBeingErnest on a different problem given here. For some reason, the percentage sign is not being displayed in my system but you can figure that out
rad = 1.2 # Define a radius variable for later use
my_pie, texts, autotexts = plt.pie(size_of_groups, radius=rad, colors=colors, autopct="%.1f%%",
pctdistance=0.75, labeldistance=0.7, textprops={'color':'white', 'size':20})
# Rest of the code
cx, cy = 0, 0 # Center of the pie chart
for t in autotexts:
x, y = t.get_position()
text = t.get_text()
if float(text.strip('%')) < 1: # Here 1 is the target threshold percentage
angle = np.arctan2(y-cy, x-cx)
xt, yt = 1.1*rad*np.cos(angle)+cx, 1.1*rad*np.sin(angle)+cy
t.set_color("k")
t.set_position((xt,yt))

Quiver basemap plot - how to make the quiver density latitude-dependent?

I want to do a quiver plot on a polar basemap plot. I have a regular lat/lon grid, and because there are more grid boxes at the higher latitudes, my code plots as many quivers at the pole as on the equator, so they overlap etc. How can I make the density of quivers latitude-dependent?
This is the code I use
import numpy as np
from mpl_toolkits.basemap import Basemap, addcyclic
import matplotlib.pyplot as plt
m_mu = Basemap(projection='npstere',boundinglat=10,lon_0=0,resolution='l',round=True)
lats=np.arange(0.,91.,15.)
lons=np.arange(-180.,181.,30.)
valin_u=np.array([[np.random.randn() for y in range(len(lons))] for x in range(len(lats))])
valin_v=np.array([[np.random.randn() for y in range(len(lons))] for x in range(len(lats))])
del x,y
valin = np.sqrt( valin_u**2 + valin_v**2 )
mu_cyclic, lons_cyclic = addcyclic(valin, lons)
mu_cyclic_u, lons_cyclic = addcyclic(valin_u, lons)
mu_cyclic_v, lons_cyclic = addcyclic(valin_v, lons)
grid = np.meshgrid( lons_cyclic, lats )
x, y = m_mu( *grid)
plt.figure()
cs = m_mu.pcolormesh(x, y, mu_cyclic)
csquiv = m_mu.quiver(x[::1,::1], y[::1,::1], mu_cyclic_u[::1,::1], mu_cyclic_v[::1,::1])
plt.show()
I guess I could write a function to set a latitude-dependent number of the values to .nan, but hopefully there is a better solution?
Many thanks for your help :-)
Sabine

Creating a hexagonal grid (u-matrix) in Python using a Regularpolycollection

I am trying to create a hexagonal grid to use with a u-matrix in Python (3.4) using a RegularPolyCollection (see code below) and have run into two problems:
The hexagonal grid is not tight. When I plot it there are empty spaces between the hexagons. I can fix this by resizing the window, but since this is not reproducible and I want all of my plots to have the same size, this is not satisfactory. But even if it were, I run into the second problem.
Either the top or right hexagons don't fit in the figure and are cropped.
I have tried a lot of things (changing figure size, subplot_adjust(), different areas, different values of d, etc.) and I am starting to get crazy! It feels like the solution should be simple, but I simply cannot find it!
import SOM
import matplotlib.pyplot as plt
from matplotlib.collections import RegularPolyCollection
import numpy as np
import matplotlib.cm as cm
from mpl_toolkits.axes_grid1 import make_axes_locatable
m = 3 # The height
n = 3 # The width
# Some maths regarding hexagon geometry
d = 10
s = d/(2*np.cos(np.pi/3))
h = s*(1+2*np.sin(np.pi/3))
r = d/2
area = 3*np.sqrt(3)*s**2/2
# The center coordinates of the hexagons are calculated.
x1 = np.array([d*x for x in range(2*n-1)])
x2 = x1 + r
x3 = x2 + r
y = np.array([h*x for x in range(2*m-1)])
c = []
for i in range(2*m-1):
if i%4 == 0:
c += [[x,y[i]] for x in x1]
if (i-1)%2 == 0:
c += [[x,y[i]] for x in x2]
if (i-2)%4 == 0:
c += [[x,y[i]] for x in x3]
c = np.array(c)
# The color of the hexagons
d_matrix = np.zeros(3*3)
# Creating the figure
fig = plt.figure(figsize=(5, 5), dpi=100)
ax = fig.add_subplot(111)
# The collection
coll = RegularPolyCollection(
numsides=6, # a hexagon
rotation=0,
sizes=(area,),
edgecolors = (0, 0, 0, 1),
array= d_matrix,
cmap = cm.gray_r,
offsets = c,
transOffset = ax.transData,
)
ax.add_collection(coll, autolim=True)
ax.axis('off')
ax.autoscale_view()
plt.show()
See this topic
Also you need to add scale on axis like
ax.axis([xmin, xmax, ymin, ymax])
The hexalattice module of python (pip install hexalattice) gives solution to both you concerns:
Grid tightness: You have full control over the hexagon border gap via the 'plotting_gap' argument.
The grid plotting takes into account the grid final size, and adds sufficient margins to avoid the crop.
Here is a code example that demonstrates the control of the gap, and correctly fits the grid into the plotting window:
from hexalattice.hexalattice import *
create_hex_grid(nx=5, ny=5, do_plot=True) # Create 5x5 grid with no gaps
create_hex_grid(nx=5, ny=5, do_plot=True, plotting_gap=0.2)
See this answer for additional usage examples, more images and links
Disclosure: the hexalattice module was written by me

Plotting a pie-chart in matplotlib at a specific angle with the fracs on the wedges

I am plotting a piechart with matplotlib using the following code:
ax = axes([0.1, 0.1, 0.6, 0.6])
labels = 'Twice Daily', 'Daily', '3-4 times per week', 'Once per week','Occasionally'
fracs = [20,50,10,10,10]
explode=(0, 0, 0, 0,0.1)
patches, texts, autotexts = ax.pie(fracs, labels=labels, explode = explode,
autopct='%1.1f%%', shadow =True)
proptease = fm.FontProperties()
proptease.set_size('xx-small')
setp(autotexts, fontproperties=proptease)
setp(texts, fontproperties=proptease)
rcParams['legend.fontsize'] = 7.0
savefig("pie1")
This produces the following piechart.
However, I want to start the pie-chart with the first wedge on top, the only solution I could find for this was using this code
However on using this as below,
from pylab import *
from matplotlib import font_manager as fm
from matplotlib.transforms import Affine2D
from matplotlib.patches import Circle, Wedge, Polygon
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
labels = 'Twice Daily', 'Daily', '3-4 times per week', 'Once per week','Occasionally'
fracs = [20,50,10,10,10]
wedges, plt_labels = ax.pie(fracs, labels=labels)
ax.axis('equal')
starting_angle = 90
rotation = Affine2D().rotate(np.radians(starting_angle))
for wedge, label in zip(wedges, plt_labels):
label.set_position(rotation.transform(label.get_position()))
if label._x > 0:
label.set_horizontalalignment('left')
else:
label.set_horizontalalignment('right')
wedge._path = wedge._path.transformed(rotation)
plt.savefig("pie2")
This produces the following pie chart
However, this does not print the fracs on the wedges as in the earlier pie chart. I have tried a few different things, but I am not able to preserve the fracs. How can I start the first wedge at noon and display the fracs on the wedges as well??
Ordinarily I wouldn't recommend changing the source of a tool, but it's hacky to fix this outside and easy inside. So here's what I'd do if you needed this to work Right Now(tm), and sometimes you do..
In the file matplotlib/axes.py, change the declaration of the pie function to
def pie(self, x, explode=None, labels=None, colors=None,
autopct=None, pctdistance=0.6, shadow=False,
labeldistance=1.1, start_angle=None):
i.e. simply add start_angle=None to the end of the arguments.
Then add the five lines bracketed by "# addition".
for frac, label, expl in cbook.safezip(x,labels, explode):
x, y = center
theta2 = theta1 + frac
thetam = 2*math.pi*0.5*(theta1+theta2)
# addition begins here
if start_angle is not None and i == 0:
dtheta = (thetam - start_angle)/(2*math.pi)
theta1 -= dtheta
theta2 -= dtheta
thetam = start_angle
# addition ends here
x += expl*math.cos(thetam)
y += expl*math.sin(thetam)
Then if start_angle is None, nothing happens, but if start_angle has a value, then that's the location that the first slice (in this case the 20%) is centred on. For example,
patches, texts, autotexts = ax.pie(fracs, labels=labels, explode = explode,
autopct='%1.1f%%', shadow =True, start_angle=0.75*pi)
produces
Note that in general you should avoid doing this, patching the source I mean, but there are times in the past when I've been on deadline and simply wanted something Now(tm), so there you go..

Categories