Contourf and quiver Animation with Basemap Python - python

I have two different datasets with different (lat, lon) grid over a common region. I am trying to plot a contourf of one and quiver of another on a common basemap, and then animate this over time.
I have followed this http://matplotlib.org/basemap/users/examples.html and this https://github.com/matplotlib/basemap/blob/master/examples/animate.py.
So far I have:
m = Basemap(llcrnrlon=min(lon),llcrnrlat=min(lat),urcrnrlon=max(lon),urcrnrlat=max(lat),
rsphere=(6378137.00,6356752.3142),resolution='h',projection='merc')
# first dataset
lons, lats = numpy.meshgrid(lon, lat)
X, Y = m(lons, lats)
# second dataset
lons2, lats2 = numpy.meshgrid(lon2, lat2)
xx, yy = m(lons2, lats2)
#colormap
levels = numpy.arange(0,3,0.1)
cmap = plt.cm.get_cmap("gist_rainbow_r")
# create figure.
fig=plt.figure(figsize=(12,8))
ax = fig.add_axes([0.05,0.05,0.8,0.85])
# contourf
i = 0
CS = m.contourf(xx,yy,AUX[i,:,:],levels,cmap=cmap,extend='max')
cbar=plt.colorbar(CS)
# quiver
x = X[0::stp,0::stp] #plot arrows with stp = 2
y = Y[0::stp,0::stp]
uplt = U[i,0::stp,0::stp]
vplt = V[i,0::stp,0::stp]
Q = m.quiver(x,y,uplt,vplt,color='k',scale=15)
qk = ax.quiverkey(Q,0.1,0.1,0.5,'0.5m/s')
# continents
m.drawcoastlines(linewidth=1.25)
m.fillcontinents(color='0.8')
def updatefig(i):
global CS, Q
for c in CS.collections: c.remove()
CS = m.contourf(xx,yy,AUX[i,:,:],levels,cmap=cmap,extend='max')
uplt = U[i,0::stp,0::stp]
vplt = V[i,0::stp,0::stp]
Q.set_UVC(uplt,vplt)
anim = animation.FuncAnimation(fig, updatefig, frames=AUX.shape[0],blit=False)
plt.show()
Everything works fine for the first plot (i=0) but afterwards I only get the contourf animation without any quiver plot superimposed (but the quiverkey appears!)
Both animations separately work fine, but not together.
Is there a problem of having two different x,y on a basemap?

You can try ax.autoscale(False) before you plot the second part(quiver).
Hope it'll be helpful

I was able to work it out by adding the quiver plot inside the function and adding a Q.remove() after saving the plot.
It ended with something like:
def updatefig(i):
global CS, Q
for c in CS.collections: c.remove()
CS = m.contourf(xx,yy,AUX[i,:,:],levels,cmap=cmap,extend='max')
uplt = U[i,0::stp,0::stp]
vplt = V[i,0::stp,0::stp]
Q = m.quiver(x,y,uplt,vplt,color='k',scale=15)
# SAVE THE FIGURE
Q.remove() #after saving the figure
anim = animation.FuncAnimation(fig, updatefig, frames=AUX.shape[0],blit=False)
plt.show()
It works like I intended although I still can't find the answer I set_UVC() does not work with contourf...

Related

Matplotlib contourf to represent "hyperbolic" data

I want to plot a 3-D function z=f(x,y) in contourf fashion, while highlighting some levels where z is constant with black lines and custom labels. The x,y,z data points are contained in a *.csv file which I manipulate with pandas. The function f(x,y) (hence the z-points) have a scaling of type f(x,y) proprtional to 1/(xy)*
This is the result I got with my code
As you can see, there's no good scaling results on the colors. I.e. all lines above 99% (which take up the vast majority of the image space) are the same color, whereas I'd like to have a different shade for every "section" (i.e. one shade between 98% and 98.5%, another one between 98.5% and 99%, etc). The colorbar is also weird, and I guess this is due to the wrong scaling in the image, to being with. How do I obtain the wanted result? Hereby the code I'm using as of now (it should be plug-and-play).
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams.update({
"text.usetex": True,
"font.family": "sans-serif",
"font.sans-serif": "Helvetica",
})
rcParams['text.latex.preamble'] = r'\usepackage{amsmath}' #for \text command
# This custom formatter removes trailing zeros, e.g. "1.0" becomes "1", and
# then adds a percent sign.
def fmt(x):
s = f"{x:.2f}"
if s.endswith("0"):
s = f"{x:.1f}"
if s.endswith("0"):
s = f"{x:.0f}"
return rf"{s} \%" if plt.rcParams["text.usetex"] else f"{s} %"
contour_data = pd.read_csv('MyData.csv', header=None, names=['x','y','z'])
Z = contour_data.pivot_table(index='x', columns='y', values='z', dropna=False).T.values
X_unique = np.sort(contour_data.x.unique())
Y_unique = np.sort(contour_data.y.unique())
X, Y = np.meshgrid(X_unique, Y_unique)
# Initialize plot objects
rcParams['figure.figsize'] = 5, 5 # sets plot size
fig = plt.figure()
ax = fig.add_subplot(111)
# Define levels in z-axis where we want lines to appear
levels = np.array([98.,98.5,99.,99.5,99.6,99.7,99.8,99.85])
# Generate a color mapping of the levels we've specified
cpf = ax.contourf(X,Y,Z,
len(levels),
extend='both',
cmap='Reds'
)
fig.colorbar(cpf, ticks=levels, orientation='vertical')
# Set all level lines to black
line_colors = ['black' for l in cpf.levels]
# Make plot and customize axes
cp = ax.contour(X, Y, Z,
levels=levels,
colors=line_colors)
ax.clabel(cp, cp.levels, inline=True, fmt=fmt, fontsize=10, colors=line_colors)
ax.set_xlabel(r'$X-\text{axis } [\alpha]$')
ax.set_ylabel(r'$Y-\text{axis } [\beta]$')
#plt.savefig('figure_for_stackoverflow.pdf') # uncomment to save vector/high-res version
The dataset can be found here
The only additional requirement I have is that I'd not like to add any other modules to my code if possible. In case the result I'm seeking is impossible to obtain with the imported modules, I'm ready to relax this constraint. In this particular dataset there are no "NaN" elements in Z, but as a general rule I'd like the NaN spots in Z to be white. Thanks for you help!
I tried plotting a surface and highlight constant lines of said surface using a contour/contourf but the result I obtain is not consisted with how my dataset is structured.
Use a BoundaryNorm.
from matplotlib.colors import BoundaryNorm
...
# note that I have extended your levels vector,
# on the bottom and on the top, to cover the full range of z
levels = np.array([z.min(), 98.,98.5,99.,99.5,99.6,99.7,99.8,99.85, z.max()])
cf = plt.contourf(x, y, z,
levels=levels,
norm=colors.BoundaryNorm(levels,256), cmap='Reds')
ct = plt.contour(x, y, z, levels=levels, colors='k k k k k k w w w w'.split())
cl = plt.clabel(ct)
cb = plt.colorbar(cf)
plt.show()
UPDATE
Even better: use extend='both' in contourf and in colorbar,
levels = np.array([98.,98.5,99.,99.5,99.6,99.7,99.8,99.85])
cf = plt.contourf(x, y, z,
levels=levels,
extend='both',
cmap='Reds')
ct = plt.contour(x, y, z, levels=levels, colors='k k k k k w w w'.split())
cl = plt.clabel(ct)
cb = plt.colorbar(cf, extend='both')
plt.show()
Notice the pointed ends in the Colorbar

Displaying scatterplot points by points in jupyter notebook

I am trying to display a scatterplot points by points from two arrays :
x = [0,1,2,3,4,5,6,7,8,9,10]
y = [0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5]
I would like to display the point (0,0.5) and add successively the other points ((0.5,1) through (0.5,10)) to the existing plot.
It could be considered as an animated scatterplot indenting points by points.
So far, I have tried the following solutions :
xi=[]
yi=[]
for i in range(10):
xi.append(x[i])
yi.append(y[i])
plt.axhline(y=0.5,color="black",linestyle = '-')
plt.scatter(xi,yi,marker = '+', color="red")
plt.legend()
plt.pause(0.01)
plt.show()
which works perfectly fine in my script (spyder IDE) but not in my jupyter notebook.
Or with the animation function from matplotlib :
frames=20
fig = plt.figure()
ax = plt.gca()
x = [0,1,2,3,4,5,6,7,8,9,10]
y = [0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5]
def scatter_ani(i):
plt.scatter(x[i], y[i],marker = '+', color="red",label="chocs")
anim = animation.FuncAnimation(fig, scatter_ani, frames = frames, interval=50)
anim.save(r"mypath/myanim.gif",writer = animation.PillowWriter(fps=30))
and then,
![mygif](myanim.gif)
in a markdown cell.
How can I display this simple animation in my notebook?
Thank you for your time, I look forward to your insights !

Wrong arrow length using quiver with cartopy projections

I want to plot a vector field with vectors representing a displacement between one point to another on the map with cartopy.
My code works as expected when using the PlateCarree() transformation, but arrow length is several orders of magnitude off for all the other projections I tested.
Here is a MWE that should illustrate quite clearly the issue:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import numpy
# Want to test for several different projections
projections = [
ccrs.PlateCarree(),
ccrs.EqualEarth(),
ccrs.Mollweide(),
ccrs.AzimuthalEquidistant(),
]
# ALl the coordinates will be given in the PlateCarree coordinate system.
coordinate_ccrs = ccrs.PlateCarree()
# We want N**2 points over the latitude/longitude values.
N = 5
lat, lon = numpy.meshgrid(numpy.linspace(-80, 80, N), numpy.linspace(-170, 170, N))
lat, lon = lat.flatten(), lon.flatten()
# We want arrows to appear, let make a small perturbation and try
# to do an arrow from (lon, lat) to (lon + perturbation, lat + perturbation).
rng = numpy.random.default_rng()
perturbation_amplitude = 10
lat_perturbation = perturbation_amplitude * rng.random(N * N)
lon_perturbation = perturbation_amplitude * rng.random(N * N)
# Create the matplotlib figure and axes, no projection for the moment as this
# will be changed later.
fig, axes = plt.subplots(2, 2)
axes = axes.flatten()
for i, projection in enumerate(projections):
# Replace the existing ax with an ax with the desired projection.
ax = axes[i]
fig.delaxes(ax)
ax = axes[i] = fig.add_subplot(2, 2, i + 1, projection=projection)
# Make the plot readable.
ax.set_global()
ax.gridlines(draw_labels="x")
# Non pertubed points are plotted in black.
ax.plot(lon, lat, "k.", ms=5, transform=coordinate_ccrs)
# Perturbed points are plotted in red.
ax.plot(
lon + lon_perturbation,
lat + lat_perturbation,
"r.",
ms=5,
transform=coordinate_ccrs,
)
# We try to draw arrows from a given black dot to its corresponding
# red dot.
ax.quiver(
lon,
lat,
lon_perturbation,
lat_perturbation,
transform=coordinate_ccrs,
# From https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.quiver.html?highlight=quiver#matplotlib.axes.Axes.quiver
# look at the documentation of the "scale_unit" parameter.
# The next 3 parameters are what matplotlib tell us to do. From
# matplotlib documentation:
# To plot vectors in the x-y plane, with u and v having the same units
# as x and y, use angles='xy', scale_units='xy', scale=1.
angles="xy",
scale_units="xy",
scale=1,
# Simply make the arrows nicer, removing these last 3 parameters do not
# solve the issue.
minshaft=2,
minlength=0.5,
width=0.002,
)
# Show everything
plt.show()
which display on the screen the following image:
The PlateCarree transformation is the only one displaying arrows. In fact, there are arrows in the other 3 projections, but I order to see them I need to scale them by 10000 with scale=0.00001 in the call to quiver, which gives:
Did I make a mistake when using cartopy API, is this expected behaviour and I missed something in the documentation, or is this a bug?
while there's quite some debate on github about cartopy's implementation of quiver-plot transformations GitHub-issues there is in fact a way on how to get your plot look as you want it to look...
However, while thinking about this... I noticed that there's a thing that you might want to consider when using projected quiver-plots...
As I see it, the re-projected arrows would would most probably need to be curved to really visualize the same direction as provided in the original data!
(in the input-crs the arrow points as a straight line from point A to B, but if you re-project the points, the "straight line" that connected A and B is now in general a curved line, and so if the original direction was correct, I think the new direction should be indicated as a curved arrow...)
That being said, you could achieve what you want by transforming the points manually instead of letting cartopy do the job:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import numpy
# Want to test for several different projections
projections = [
ccrs.PlateCarree(),
ccrs.EqualEarth(),
ccrs.Mollweide(),
ccrs.AzimuthalEquidistant(),
]
# ALl the coordinates will be given in the PlateCarree coordinate system.
coordinate_ccrs = ccrs.PlateCarree()
# We want N**2 points over the latitude/longitude values.
N = 5
lat, lon = numpy.meshgrid(numpy.linspace(-80, 80, N), numpy.linspace(-170, 170, N))
lat, lon = lat.flatten(), lon.flatten()
# We want arrows to appear, let make a small perturbation and try
# to do an arrow from (lon, lat) to (lon + perturbation, lat + perturbation).
rng = numpy.random.default_rng()
perturbation_amplitude = 10
lat_perturbation = perturbation_amplitude * rng.random(N * N)
lon_perturbation = perturbation_amplitude * rng.random(N * N)
# Create the matplotlib figure and axes, no projection for the moment as this
# will be changed later.
fig, axes = plt.subplots(2, 2)
axes = axes.flatten()
for i, projection in enumerate(projections):
# Replace the existing ax with an ax with the desired projection.
ax = axes[i]
fig.delaxes(ax)
ax = axes[i] = fig.add_subplot(2, 2, i + 1, projection=projection)
# Make the plot readable.
ax.set_global()
ax.gridlines(draw_labels="x")
# Non pertubed points are plotted in black.
ax.plot(lon, lat, "k.", ms=5, transform=coordinate_ccrs)
# Perturbed points are plotted in red.
ax.plot(
lon + lon_perturbation,
lat + lat_perturbation,
"r.",
ms=5,
transform=coordinate_ccrs,
)
xy_start = projection.transform_points(coordinate_ccrs, lon, lat)[:,:-1].T
xy_end = projection.transform_points(coordinate_ccrs, lon + lon_perturbation,
lat + lat_perturbation)[:,:-1].T
# We try to draw arrows from a given black dot to its corresponding
# red dot.
ax.quiver(
*xy_start,
*(xy_end - xy_start),
# From https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.quiver.html?highlight=quiver#matplotlib.axes.Axes.quiver
# look at the documentation of the "scale_unit" parameter.
# The next 3 parameters are what matplotlib tell us to do. From
# matplotlib documentation:
# To plot vectors in the x-y plane, with u and v having the same units
# as x and y, use angles='xy', scale_units='xy', scale=1.
angles="xy",
scale_units="xy",
scale=1,
# Simply make the arrows nicer, removing these last 3 parameters do not
# solve the issue.
minshaft=2,
minlength=0.5,
width=0.002,
)
# Show everything
plt.show()

Cartesian zoom with polar plot in python

I am trying to plot some data in polar coordinates (I am currently using the polar projection):
The code I am using is the following:
import matplotlib.pyplot as plt
import numpy as np
# Create radial and angular array
r = np.linspace(1.0,10,11)
t = np.linspace(0.0,0.5*np.pi,101)
# Define the quantity that I want to plot
z = np.zeros((len(t),len(r)))
for yval in range(len(r)):
z[:,yval] = np.cos(16.0*t)/r[yval]
#Create the figure
f = plt.figure(figsize=(13,8))
ax = plt.subplot(111, projection='polar')
ax.set_rorigin(-1)
#Plot the data
pcm = ax.pcolormesh(t,r,z.T,cmap = 'hot',shading='gouraud')
ax.set_xlim([0.0,0.5*np.pi])
ax.set_ylim([1.0,10.0])
#Add colorbar and show
bar = f.colorbar(pcm)
plt.show()
So far I have no problem, but I would like to zoom on a particular region of this plot.
However, when I set the axes range the axes is still polar, therefore I cannot zoom on a "cartesian" region of the domain (i.e. a square box).
A possible option would be to transform the data into cartesian coordinates, but when I do it I lose a lot of resolution in the inner part of the domain, which is something that I should absolutely avoid.
How can I select a rectangular zone of a plot in polar coordinates without transforming by hand the data? And in case I have to switch to cartesian coordinates, is there any matplotlib or python function that does it while taking care of the resolution in the inner regions of the domain?
Thanks in advance
You can create an X, Y mesh yourself that is has a higher resolution on the inner part of the domain and use that with ax.pcolormesh()
# Create radial and angular array
r = np.linspace(1.0,10,11)
t = np.linspace(0.0,0.5*np.pi,101)
# Define the quantity that I want to plot
z = np.zeros((len(t),len(r)))
for yval in range(len(r)):
z[:,yval] = np.cos(16.0*t)/r[yval]
#Create the figure, bigger figsize to make the resulting plot square
f = plt.figure(figsize=(13,10))
ax = plt.subplot(111) # Drop back to XY coordinates
# Generate the XY corners of the colormesh
X = np.array([[ri*np.cos(j) for j in t] for ri in r])
Y = np.array([[ri*np.sin(j) for j in t] for ri in r])
#Plot the data
pcm = ax.pcolormesh(X,Y,z.T,cmap = 'hot',shading='gouraud')
#Add colorbar and show
bar = f.colorbar(pcm)
plt.show()
The figure from the question
The figure generated by code above
A way to do this is to create an expanded polar plot and then clip a rectangle of it. A picture is worth a thousand words:
Here is a function that allows you to do so. The arguments are the original axes, the xlims and ylims of the region to be zoomed and the inset axes bounds (x0, y0, width, height) in the original axes coordinates. The function outputs a cartesian ax with the specified limits, a polar axes where you can plot and the rmax value you need to set AFTER plotting (if you do it before, it will change after plotting).
def create_polar_zoom_inset(ax, xlims, ylims, inset_bounds):
# Create cartesian axes for inset
ax_inset_cart = ax.inset_axes(inset_bounds)
ax_inset_cart.set_xlim(xlims)
ax_inset_cart.set_ylim(ylims)
# Calculate location of expanded polar inset
# Scale factor from data to axes coordinates
xscalefactor = inset_bounds[2]/(xlims[1] - xlims[0])
yscalefactor = inset_bounds[3]/(ylims[1] - ylims[0])
# Center of expanded polar inset
center_inset_polar = [
inset_bounds[0] - xlims[0]*xscalefactor,
inset_bounds[1] - ylims[0]*yscalefactor
]
# Max value of r in the inset
rmax_inset = 2*np.sqrt(np.power(xlims, 2).max() + np.power(ylims, 2).max())
# Size of the expanded polar inset
size_inset_polar = [2*rmax_inset*xscalefactor, 2*rmax_inset*yscalefactor]
# Create expanded polar inset
polar_inset_bounds = [
center_inset_polar[0] - 0.5*size_inset_polar[0],
center_inset_polar[1] - 0.5*size_inset_polar[1],
size_inset_polar[0],
size_inset_polar[1]
]
ax_inset_polar = ax.inset_axes(polar_inset_bounds, projection="polar")
ax_inset_polar.set_facecolor("None")
# Remove tick labels from expanded polar inset
ax_inset_polar.xaxis.set_ticklabels([])
ax_inset_polar.yaxis.set_ticklabels([])
# Clip elements of the expanded inset outside the cartesian inset
ax_inset_polar.patch = ax_inset_cart.patch
for axis in [ax_inset_polar.xaxis, ax_inset_polar.yaxis]:
axis.set_clip_path(ax_inset_cart.patch)
ax_inset_polar.spines['polar'].set_clip_path(ax_inset_cart.patch)
return ax_inset_cart, ax_inset_polar, rmax_inset
The code in your example is especially hard since the origin of the axes is not (0,0) but (-1,-1). That would need additional tinkering. But if we set rorigin to 0 (as it will be usually the case), the code would look as follows
# Create radial and angular array
r = np.linspace(1.0,10,11)
t = np.linspace(0.0,0.5*np.pi,101)
# Define the quantity that I want to plot
z = np.zeros((len(t),len(r)))
for yval in range(len(r)):
z[:,yval] = np.cos(16.0*t)/r[yval]
#Create the figure
f = plt.figure(figsize=(13,8))
ax = plt.subplot(111, projection='polar')
ax.set_rorigin(0)
#Plot the data
pcm = ax.pcolormesh(t,r,z.T,cmap = 'hot',shading='gouraud')
ax.set_xlim([0.0,0.5*np.pi])
ax.set_ylim([1.0,10.0])
#Add colorbar and show
bar = f.colorbar(pcm)
#Create inset
ax_c, ax_p, rmax_inset = create_polar_zoom_inset(
ax, xlims=[0., 2.], ylims=[1, 2], inset_bounds=[0.4, 0.3, 0.6, 0.3])
#Plot on inset
ax_p.pcolormesh(t,r,z.T,cmap = 'hot',shading='gouraud')
#Make rorigin and rmin coincide with the original plot
ax_p.set_rorigin(0)
ax_p.set_rmin(1)
#Set rmax
ax_p.set_rmax(rmax_inset)
plt.show()

Change point color in python plot animation

I have a list of points, lets say as (x,y) pairs. I am trying to animate a plot so that each frame of the animation, a new point show up on the plot in a different color. Specifically on the 0th frame, the 0th point appears, on the the 1st frame, the 1st point appears, and so on. I would also like to have these points appear in a new color, specifically like a linear progression through a color palette as the points progress, so that you can "follow" the points by their color. This is similar to, and how I got as far as I am now: How can i make points of a python plot appear over time?. The first animation in the link is spot on, except without the points changing colors.
I am using matplotlib, matplotlib.pyplot, and FuncAnimation from matplotlib.animation
What I have already:
def plot_points_over_time(list_of_points):
num_points = len(list_of_points)
fig = plt.figure()
x, y = zip(*list_of_points)
plt.xlim(min(x),max(x))
plt.ylim(min(y),max(y))
colors = [plt.cm.gist_rainbow(each) for each in np.linspace(0,1,num_points)]
graph, = plt.plot([],[],'o')
def animate(i):
graph.set_data(x[:i+1],y[:i+1])
return graph
ani = FuncAnimation(fig, animate, frames = num_points, repeat = False, interval = 60000/num_points)
plt.show()
I can change the color of all of the points together on each frame by including the line graph.set_color(colors[i]) in the animate function, but not each point individually.
Figured it out with some digging and trial and error:
def plot_points_over_time(list_of_points):
num_points = len(list_of_points)
fig = plt.figure()
x, y = zip(*list_of_points)
plt.xlim(min(x),max(x))
plt.ylim(min(y),max(y))
colors = [plt.cm.gist_rainbow(each) for each in np.linspace(0,1,num_points)]
scat, = plt.plot([],[])
def animate(i):
scat.set_offsets(np.c_[x[:i+1], y[:i+1]])
scat.set_color(colors[:i+1])
return scat,
ani = FuncAnimation(fig, animate, frames = num_points, repeat = False, interval = 60000/num_points)
plt.show()

Categories