I'm trying to plot scatter data of storm reports on top of radar gridded data and I seem to be getting strange plotting issues related to mapping using cartopy. See example image attached. It appears that the scatter data plots on a separate axis than the radar data, but I'm not sure why given that the plotting module for the radar data uses the same user input for min/max lat/lon and the chosen projection. Additionally, the lat/lon range on the map is dynamic as I loop through time stamps. I know I can use an ax.set_extent to create fixed coordinates, but this does not solve my issue of the plotting being done on a separate axis. Does anyone have any suggestions? They should overlay on the same axis.
Here is the code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.dates as mdates
import cartopy.crs as ccrs
import pyart
import pandas as pd
import nexradaws
import tempfile
import pytz
templocation = tempfile.mkdtemp()
import cartopy.feature as cfeature
from metpy.plots import USCOUNTIES
### Define the radar, start time and end time
radar_id = 'KDVN'
start = pd.Timestamp(2020,8,10,16,30).tz_localize(tz='UTC')
end = pd.Timestamp(2020,8,10,21,0).tz_localize(tz='UTC')
### Bounds of map we want to plot
min_lon = -93.25
max_lon = -88.
min_lat = 40.35
max_lat = 43.35
# ### Bounds of map we want to plot
# min_lon = -80.8
# max_lon = -77.
# min_lat = 34
# max_lat = 37
#### and get the data
conn = nexradaws.NexradAwsInterface()
scans = conn.get_avail_scans_in_range(start, end, radar_id)
print("There are {} scans available between {} and {}\n".format(len(scans), start, end))
print(scans[0:4])
## download these files
#results = conn.download(scans[0:2], templocation)
results = conn.download(scans, templocation)
#%%
#Now get the severe reports from the SPC site. This assumes you're plotting a year far #enough in the past that
# SPC has official records available. If plotting a more recent time period, then the #local storm reports archive
#[![enter image description here][1]][1] at IEM is a good source
### wind reports
wind_rpts = pd.read_csv("https://www.spc.noaa.gov/wcm/data/"+str(start.year)+"_wind.csv")
wind_rpts['datetime'] = pd.to_datetime(wind_rpts.date + ' ' + wind_rpts.time) ## convert to datetime
wind_rpts.set_index("datetime",inplace=True)
### times in the file are given in central standard time (UTC+6). Localize, and convert to UTC
wind_rpts.index = wind_rpts.index.tz_localize("Etc/GMT+6",ambiguous='NaT',nonexistent='shift_forward').tz_convert("UTC")
## subset down to 30 minutes before/after the radar times we're plotting
wind_rpts = wind_rpts[((start-pd.Timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M")):((end+pd.Timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M"))]
wind_rpts
### repeat for tornado reports
tor_rpts = pd.read_csv("https://www.spc.noaa.gov/wcm/data/"+str(start.year)+"_torn.csv")
tor_rpts['datetime'] = pd.to_datetime(tor_rpts.date + ' ' + tor_rpts.time) ## convert to datetime
tor_rpts.set_index("datetime",inplace=True)
### times in the file are given in central standard time (UTC+6). Localize, and convert to UTC
tor_rpts.index = tor_rpts.index.tz_localize("Etc/GMT+6",ambiguous='NaT',nonexistent='shift_forward').tz_convert("UTC")
## subset down to 30 minutes before/after the radar times we're plotting
tor_rpts = tor_rpts[((start-pd.Timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M")):((end+pd.Timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M"))]
tor_rpts
### repeat for hail
hail_rpts = pd.read_csv("https://www.spc.noaa.gov/wcm/data/"+str(start.year)+"_hail.csv")
hail_rpts['datetime'] = pd.to_datetime(hail_rpts.date + ' ' + hail_rpts.time) ## convert to datetime
hail_rpts.set_index("datetime",inplace=True)
### times in the file are given in central standard time (UTC+6). Localize, and convert to UTC
hail_rpts.index = hail_rpts.index.tz_localize("Etc/GMT+6",ambiguous='NaT',nonexistent='shift_forward').tz_convert("UTC")
## subset down to 30 minutes before/after the radar times we're plotting
hail_rpts = hail_rpts[((start-pd.Timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M")):((end+pd.Timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M"))]
hail_rpts
#%%
'''Now we plot the maps and animate'''
### loop over the radar images that have been downloaded
for i,scan in enumerate(results.iter_success(),start=1):
#for i in range(0,1):
## skip the files ending in "MDM"
if scan.filename[-3:] != "MDM":
print(str(i))
print("working on "+scan.filename)
this_time = pd.to_datetime(scan.filename[4:17], format="%Y%m%d_%H%M").tz_localize("UTC")
radar = scan.open_pyart()
#display = pyart.graph.RadarDisplay(radar)
fig = plt.figure(figsize=[15, 7])
map_panel_axes = [0.05, 0.05, .4, .80]
x_cut_panel_axes = [0.55, 0.10, .4, .25]
y_cut_panel_axes = [0.55, 0.50, .4, .25]
projection = ccrs.PlateCarree()
## apply gatefilter (see here: https://arm-doe.github.io/pyart/notebooks/masking_data_with_gatefilters.html)
#gatefilter = pyart.correct.moment_based_gate_filter(radar)
gatefilter = pyart.filters.GateFilter(radar)
# Lets remove reflectivity values below a threshold.
gatefilter.exclude_below('reflectivity', -2.5)
display = pyart.graph.RadarMapDisplay(radar)
### set up plot
ax1 = fig.add_axes(map_panel_axes, projection=projection)
# Add some various map elements to the plot to make it recognizable.
ax1.add_feature(USCOUNTIES.with_scale('500k'), edgecolor="gray", linewidth=0.4)
#ax1.coastlines('50m', edgecolor='black', linewidth=0.75)
ax1.add_feature(cfeature.STATES.with_scale('10m'), linewidth=1.0)
cf = display.plot_ppi_map('reflectivity', 0, vmin=-7.5, vmax=65,
min_lon=min_lon, max_lon=max_lon, min_lat=min_lat, max_lat=max_lat,
title=radar_id+" reflectivity and severe weather reports, "+this_time.strftime("%H%M UTC %d %b %Y"),
projection=projection, resolution='10m',
gatefilter=gatefilter,
cmap='pyart_HomeyerRainbow',
colorbar_flag=False,
lat_lines=[0,0], lon_lines=[0,0]) ## turns off lat/lon grid lines
#display.plot_crosshairs(lon=lon, lat=lat)
## plot horizontal colorbar
display.plot_colorbar(cf,orient='horizontal', pad=0.07)
# Plot range rings if desired
#display.plot_range_ring(25., color='gray', linestyle='dashed')
#display.plot_range_ring(50., color='gray', linestyle='dashed')
#display.plot_range_ring(100., color='gray', linestyle='dashed')
ax1.set_xticks(np.arange(min_lon, max_lon, .5), crs=ccrs.PlateCarree())
ax1.set_yticks(np.arange(min_lat, max_lat, .5), crs=ccrs.PlateCarree())
## add marker points for severe reports
wind_rpts_now = wind_rpts[((start-pd.Timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M")):this_time.strftime("%Y-%m-%d %H:%M")]
ax1.scatter(wind_rpts_now.slon.values.tolist(), wind_rpts_now.slat.values.tolist(), s=20, facecolors='none', edgecolors='mediumblue', linewidths=1.8)
tor_rpts_now = tor_rpts[((start-pd.Timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M")):this_time.strftime("%Y-%m-%d %H:%M")]
ax1.scatter(tor_rpts_now.slon.values.tolist(), tor_rpts_now.slat.values.tolist(), s=20, facecolors='red', edgecolors='black', marker="v",linewidths=1.5)
hail_rpts_now = hail_rpts[((start-pd.Timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M")):this_time.strftime("%Y-%m-%d %H:%M")]
ax1.scatter(hail_rpts_now.slon.values.tolist(), hail_rpts_now.slat.values.tolist(), s=20, facecolors='none', edgecolors='green', linewidths=1.8)
plt.savefig(scan.radar_id+"_"+scan.filename[4:17]+"_dz_rpts.png",bbox_inches='tight',dpi=300,
facecolor='white', transparent=False)
#plt.show()
plt.close('all')
Related
I am trying to plot the groundtrack of a satellite through a combination of packages, animate the satellite movement, mark a field of view from the subsatellite point (which is just arbitrary circles in this code) and then export the file as a video of some kind. So far, I have been able to do all of this except that when I try to export the video, the Nightshade feature doesn't animate so much as overlay and eventually blacks out most of the screen. Is there something I'm missing on how to properly animate the Nightshade feature? I know that I'm essentially recreating a new feature inside the update function everytime it runs a frame but I could not figure out how to update it as I do the scatter plots.
I've included my sample code below.
import pandas as pd
from sgp4.api import WGS72
from sgp4.api import Satrec
from skyfield.api import EarthSatellite, load, N, W, wgs84
import datetime
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import matplotlib.animation as animation
from cartopy.feature.nightshade import Nightshade
# CREATE THE SATELLITE DATA
epoch = datetime.date(1949, 12, 31)
sat = Satrec()
sat.sgp4init(
WGS72, # gravity model
'i', # 'a' = old AFSPC mode, 'i' = improved mode
5, # satnum: Satellite number
(datetime.date.today() - epoch).days, # epoch: days since 1949 December 31 00:00 UT
0, # bstar: drag coefficient (1/earth radii)
6.969196665e-13, # ndot (NOT USED): ballistic coefficient (revs/day)
0.0, # nddot (NOT USED): mean motion 2nd derivative (revs/day^3)
0.1, # ecco: eccentricity
280 * np.pi / 180, # argpo: argument of perigee (radians)
50 * np.pi / 180, # inclo: inclination (radians)
275 * np.pi / 180, # mo: mean anomaly (radians)
0.0472294454407, # no_kozai: mean motion (radians/minute)
50 * np.pi / 180, # nodeo: right ascension of ascending node (radians)
)
# DEFINE A FEW BASIC PARAMETERS FOR THE PROGRAM
P = sat.mo / sat.no_kozai # min, period of orbit. LEOs orbit between 84-127 minutes
ts = load.timescale()
sat1 = EarthSatellite.from_satrec(sat, ts)
hours = np.arange(0, 6, 0.05)
time = ts.utc(2021, 6, 31, hours)
pos = sat1.at(time).position.km
pos_ec = sat1.at(time).ecliptic_position().km
sp = wgs84.subpoint(sat1.at(time))
latitude = sp.latitude
longitude = sp.longitude
elev = sp.elevation
# CREATE A DATAFRAME OF THE DATA FOR REVIEW LATER IF NEEDED
df = pd.DataFrame([time.utc_datetime(), latitude.degrees, longitude.degrees, elev.km],
index=['DTS', 'lat', 'lon', 'elev']).T
df.lat = df.lat.astype('float32')
df.lon = df.lon.astype('float32')
df.elev = df.elev.astype('float32')
df.set_index('DTS', inplace=True)
# ASSIGN RELEVANT DATA FOR THE SUBSATELLITE POINT
ssp = np.transpose(np.array([longitude.degrees, latitude.degrees]))
line = ssp.copy()
pos = np.where(np.diff(np.abs(line[:, 0] >= 0)))[0]
line[pos, :] = np.nan
# CREATE DATE TIME RANGES FOR USE WITH THE NIGHTSHADE FEATURE
base = datetime.datetime(2000, 1, 1)
dates = np.array([base + datetime.timedelta(hours=i) for i in range(len(hours))])
shades = [Nightshade(date, alpha=0.2) for date in dates]
### CREATE FIGURE AND IMAGE
fig = plt.figure(figsize=(16, 8))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
ax.stock_img()
# plot lines that will show the ground track that will be animated
ax.plot(line[:, 0], line[:, 1], '--k')
# create a blank scatter to start
scatter = ax.scatter(None, None, color='r', s=30)
# initiate the circles around the scatter point
circle1 = plt.Circle((longitude.degrees[0], latitude.degrees[0]), radius=30, color='blue', alpha=0.3)
circle2 = plt.Circle((longitude.degrees[0], latitude.degrees[0]), radius=40, color='yellow', alpha=0.3)
# add the circles to the axis
ax.add_patch(circle1)
ax.add_patch(circle2)
# Add the nightshade feature (but set it to be invisible so it doesn't stay through the whole animation)
ns = ax.add_feature(Nightshade(base, alpha=0.0))
# Create all the updates for the animation
def update(i):
lon = ssp[i, 0]
lat = ssp[i, 1]
scatter.set_offsets(np.c_[lon, lat])
# add a feature for the next Nightshade feature
ns = ax.add_feature(shades[i], alpha=0.2)
circle1.center = (lon, lat)
circle2.center = (lon, lat)
return scatter, circle1, circle2, ns
# Run the animation
anim = animation.FuncAnimation(plt.gcf(), update, frames=df.shape[0],init_func=None, interval=250, blit=True)
plt.show()
# WRITE THE VIDEO
Writer = animation.writers['ffmpeg']
writer = Writer(fps=10, metadata=dict(artist='Me'), bitrate=1800)
anim.save('gt.mp4', writer=writer)
I'm plotting a graph and the x-axis label is not visible in the graph.
I have tried to solve it by adding the
ax.xaxis.labelpad = -10 # Adjust x-axis label position
Instead the x-label will overlap the ticker label
How can this be adjusted to show both x-axis label and x-ticker labels within the plot figure?
Full Code to replicate graph:
#################################
### Modules imported used ###
#################################
import pandas as pd
import numpy as np
from datetime import datetime
from datetime import date
import time
import matplotlib.pyplot as plt
import matplotlib
import matplotlib.dates as mdates
# file_path_setup = 'G:/Stocks/PowerPivotApps/Price download/'
# Performance_History = pd.read_csv(file_path_setup + 'Performance.txt', dtype=str, sep=',')
# Portfolio = Performance_History.loc[Performance_History['ExecutionType'] == 'All Portfolios']
# Portfolio = Performance_History.loc[Performance_History['ExecutionType'] == 'Selected Portfolios'] # remove "# set minimum level for performance time"
#Portfolios_Nr_of_Stocks = Portfolio['NrOfStocks']
#Portfolio_Performance_Time = Portfolio['PerformanceTime']
#Portfolio_Date = Portfolio['Date']
Portfolio_Date = ['2020-08-31','2020-09-01','2020-09-02','2020-09-02','2020-09-03','2020-09-04','2020-09-07','2020-09-08','2020-09-09','2020-09-09','2020-09-10','2020-09-11','2020-09-14','2020-09-15','2020-09-16','2020-09-17','2020-09-18','2020-09-21','2020-09-22','2020-09-22','2020-09-23','2020-09-24','2020-09-25','2020-09-28','2020-09-29','2020-09-30','2020-10-01','2020-10-02','2020-10-05','2020-10-06','2020-10-07','2020-10-08','2020-10-08','2020-10-09','2020-10-12','2020-10-13','2020-10-14','2020-10-15','2020-10-16']
Portfolio_Performance_Time =['00:11:11','00:11:07','00:11:16','00:10:42','00:10:54','00:10:46','00:10:27','00:11:23','00:11:35','00:10:23','00:10:51','00:41:22','00:11:05','00:11:15','00:10:50','00:10:41','00:19:47','00:10:43','00:10:48','00:11:12','00:11:05','00:10:45','00:11:02','00:10:57','00:11:01','00:15:17','00:14:33','00:18:49','00:14:28','00:20:45','00:14:29','00:14:45','00:17:52','00:14:37','00:14:08','00:15:05','00:14:46','00:14:39','00:14:40']
Portfolios_Nr_of_Stocks = ['621','619','617','619','622','622','622','621','622','622','622','613','622','621','621','607','621','622','621','622','620','620','622','620','620','680','679','680','681','488','681','681','680','678','678','676','678','676','676']
# Convert To integer
numberofstocks = [int(stock) for stock in Portfolios_Nr_of_Stocks]
# Convert to time
def get_sec(time_str):
"""Get Seconds from time."""
h, m, s = time_str.split(':')
return int(h) * 3600 + int(m) * 60 + int(s)
PerformanceTime = [get_sec(t) for t in Portfolio_Performance_Time]
# print(type(numberofstocks)) # print type
# convert to date series
date_portfolio = [datetime.strptime(d, '%Y-%m-%d') for d in Portfolio_Date]
# https://matplotlib.org/gallery/api/two_scales.html
# https://cmdlinetips.com/2019/10/how-to-make-a-plot-with-two-different-y-axis-in-python-with-matplotlib/
# create figure and axis objects with subplots()
fig,ax = plt.subplots(figsize=(12, 8)) # figsize -> size of the plot window
# make a plot
ax.plot(date_portfolio, PerformanceTime, color="red", marker="x")
# set x-axis label
ax.set_xlabel("Date", fontsize=14)
# set y-axis label
ax.set_ylabel("Performance Time",color="red",fontsize=14)
# set title
ax.set_title("Execution History",fontsize=20, loc="center", pad=10)
# format y-axis label to hh:mm:ss
formatter_yx1 = matplotlib.ticker.FuncFormatter(lambda s, x: time.strftime('%H:%M:%S', time.gmtime(s)))
ax.yaxis.set_major_formatter(formatter_yx1)
# rotate x-axis lables and adjust size
plt.xticks(rotation=90, ha='right')
# plt.xticks(rotation=90, ha='right', fontsize='x-small') # Small font text
# set minimum level for performance time, y-axis 1
ax.set_ylim([min(PerformanceTime)-100,25*60]) # -100 -> set minimum. 25*60 -> Set maximum
# twin object for two different y-axis on the sample plot
ax2=ax.twinx()
# make a plot with different y-axis using second axis object
ax2.plot(date_portfolio, numberofstocks,color="blue",marker="o")
# ax2.set_ylim([620, 680])
ax2.set_ylabel("Nr Of Stocks",color="blue",fontsize=14)
# set minimum level for performance time, y-axis 2
ax2.set_ylim([600, max(numberofstocks)+10]) # -100 -> set minimum. 25*60 -> Set maximum
# set date interval
ax.xaxis.set_major_locator(mdates.DayLocator(interval=7)) # max interval
ax.xaxis.set_minor_locator(mdates.DayLocator(interval=1)) # minimum interval
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')) # set date format
ax.xaxis.labelpad = -10 # Adjust x-axis label position
# Plot graph
plt.show()
You could use "Tight Layout" function in matplotlib to solve the issue.
Add the line before you plot the graph, where h_pad will adjust the height, w_pad will adjust the width.
# Adjust x-axis margins
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=5.0)
And remove this part:
ax.xaxis.labelpad = -10 # Adjust x-axis label position
Result:
I'm trying to plot blackbody wavelength vs flux for 288 Kelvin temperature (the Earth) and 6000 Kelvin temperature (the sun). I want both of these to be on the same plot and know I will need a log x-axis but I keep having issues having both lines appear. This is the code I have so far:
# Import libraries
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
# Constants
c = 3.0e8 # m/s
h = 6.626e-34 # Js
k = 1.38e-23 # J/K
c1 = 2*np.pi*h*c**2
c2 = (h*c)/k
T1 = 6000
T2 = 288
lam = np.logspace(-8,-3,2000) # Generate x-axis values
F1 = c1/(lam**5*(np.exp(c2/(lam*T1))-1)) # Calculate y-values
F1 = F1/1e9
F2 = c1/(lam**5*(np.exp(c2/(lam*T2))-1)) # Calculate y-values
F2 = F2/1e9
# Create plot
ax = plt.gca()
plt.xlabel(r'$\lambda$ (nm)')
plt.ylabel(r'$F_{BB\lambda}(W\/m^{-2}nm^{-1})$')
plt.text(0.05,.8, 'T = {0:d}K'.format(T1), transform = ax.transAxes, size = 'small')
plt.text(0.05,.3, 'T = {0:d}K'.format(T2), transform = ax.transAxes, size = 'small')
plt.xticks(), plt.yticks()
plt.semilogx(lam*1e9, F1, lam*1e9, F2, color= 'black') # Create figure and axis objects
plt.xlim(10,1e6)
plt.ylim(0,)
plt.show() # Display plot to screen
This plots the attached picture which is correct for 6000K but for some reason it's not plotting the 288K curve and I'm not sure how to fix it.
I have produced 17 global plots that show the decadal averages in maximum surface ozone from 1850-2015. Rather than plotting them individually, I wish to create an animation that cycles through them (almost like a gif), i.e. have the same coastlines, axes and colour bar throughout but change what is being plotted as the contour.
Any help on how to adapt my code to do this would be greatly appreciated - thank you in advance!!
import numpy as np
import netCDF4 as n4
import matplotlib.pyplot as plt
from matplotlib import colorbar, colors
import matplotlib.cm as cm
import cartopy as cart
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import cartopy.feature as cfeature
nc = n4.Dataset('datafile.nc','r')
# daily maximum O3 VMR (units: mol mol-1)
sfo3max = nc.variables['sfo3max']
lon = nc.variables['lon'] # longitude
lat = nc.variables['lat'] # latitude
# (I manipulate the data to produce 17 arrays containing the decadal average O3 VMR which are
# listed below in sfo3max_avg)
sfo3max_avg = [sfo3max_1850_1860_avg, sfo3max_1860_1870_avg, sfo3max_1870_1880_avg,
sfo3max_1880_1890_avg, sfo3max_1890_1900_avg, sfo3max_1900_1910_avg,
sfo3max_1910_1920_avg, sfo3max_1920_1930_avg, sfo3max_1930_1940_avg,
sfo3max_1940_1950_avg, sfo3max_1950_1960_avg, sfo3max_1960_1970_avg,
sfo3max_1970_1980_avg, sfo3max_1980_1990_avg, sfo3max_1990_2000_avg,
sfo3max_2000_2010_avg, sfo3max_2010_2015_avg]
# find overall min & max values for colour bar in plots
min_sfo3max_avg = np.array([])
for i in sfo3max_avg:
sfo3max_avg_min = np.amin(i)
min_sfo3max_avg = np.append(min_sfo3max_avg, sfo3max_avg_min)
overall_min_sfo3max_avg = np.amin(min_sfo3max_avg)
max_sfo3max_avg = np.array([])
for i in sfo3max_avg:
sfo3max_avg_max = np.amax(i)
max_sfo3max_avg = np.append(max_sfo3max_avg, sfo3max_avg_max)
overall_max_sfo3max_avg = np.amax(max_sfo3max_avg)
# finally plot the 17 global plots of sfo3max_avg
for k in sfo3max_avg:
fig = plt.figure()
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines() # Adding coastlines
cs = ax.contourf(lon[:], lat[:], k[:], cmap='magma')
ax.set_title('Decadal Average of Maximum O3 Volume Mixing Ratio')
m = plt.cm.ScalarMappable(cmap=cm.magma)
m.set_array(i[:])
m.set_clim(overall_min_sfo3max_avg, overall_max_sfo3max_avg)
# Additional necessary information
cbar = plt.colorbar(m, boundaries=np.arange(overall_min_sfo3max_avg, overall_max_sfo3max_avg
+ 0.5e-08, 0.5e-08))
cbar.set_label('mol mol-1')
# Adding axis labels - latitude & longitude
gridl = ax.gridlines(color="black", linestyle="dotted", draw_labels=True)
gridl.xformatter=LONGITUDE_FORMATTER
gridl.yformatter=LATITUDE_FORMATTER
gridl.xlabels_top = False
gridl.ylabels_right = False
fig.set_size_inches(w=20,h=10)
plt.show() # show global plot
Several elements in your plotting can be kept out of the loop because they only need to be set up once. After you set up the plot elements you can update the plot and animate by looping over the list. This can be achieved by making use of matplotlib's interactive mode as shown in the code below:
import numpy as np
import netCDF4 as n4
import matplotlib
matplotlib.use("nbagg")
import matplotlib.pyplot as plt
from matplotlib import colorbar, colors
import matplotlib.cm as cm
import cartopy as cart
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import cartopy.feature as cfeature
nc = n4.Dataset('datafile.nc','r')
# daily maximum O3 VMR (units: mol mol-1)
sfo3max = nc.variables['sfo3max']
lon = nc.variables['lon'] # longitude
lat = nc.variables['lat'] # latitude
# (I manipulate the data to produce 17 arrays containing the decadal average O3 VMR which are
# listed below in sfo3max_avg)
sfo3max_avg = [sfo3max_1850_1860_avg, sfo3max_1860_1870_avg, sfo3max_1870_1880_avg,
sfo3max_1880_1890_avg, sfo3max_1890_1900_avg, sfo3max_1900_1910_avg,
sfo3max_1910_1920_avg, sfo3max_1920_1930_avg, sfo3max_1930_1940_avg,
sfo3max_1940_1950_avg, sfo3max_1950_1960_avg, sfo3max_1960_1970_avg,
sfo3max_1970_1980_avg, sfo3max_1980_1990_avg, sfo3max_1990_2000_avg,
sfo3max_2000_2010_avg, sfo3max_2010_2015_avg]
# find overall min & max values for colour bar in plots
min_sfo3max_avg = np.array([])
for i in sfo3max_avg:
sfo3max_avg_min = np.amin(i)
min_sfo3max_avg = np.append(min_sfo3max_avg, sfo3max_avg_min)
overall_min_sfo3max_avg = np.amin(min_sfo3max_avg)
max_sfo3max_avg = np.array([])
for i in sfo3max_avg:
sfo3max_avg_max = np.amax(i)
max_sfo3max_avg = np.append(max_sfo3max_avg, sfo3max_avg_max)
overall_max_sfo3max_avg = np.amax(max_sfo3max_avg)
#setup the plot elements
fig = plt.figure()
fig.set_size_inches(w=20,h=10)
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines() # Adding coastlines
ax.set_title('Decadal Average of Maximum O3 Volume Mixing Ratio')
m = plt.cm.ScalarMappable(cmap=cm.magma)
m.set_array(i[:])
m.set_clim(overall_min_sfo3max_avg, overall_max_sfo3max_avg)
# Additional necessary information
cbar = plt.colorbar(m, boundaries=np.arange(overall_min_sfo3max_avg, overall_max_sfo3max_avg
+ 0.5e-08, 0.5e-08))
cbar.set_label('mol mol-1')
# plot here only the 1st item in your sfo3max_avg list.
cs = ax.contourf(lon[:], lat[:], sfo3max_avg[0][:], cmap='magma')
# Adding axis labels - latitude & longitude
gridl = ax.gridlines(color="black", linestyle="dotted", draw_labels=True)
gridl.xformatter=LONGITUDE_FORMATTER
gridl.yformatter=LATITUDE_FORMATTER
gridl.xlabels_top = False
gridl.ylabels_right = False
plt.ion() # set interactive mode
plt.show()
# finally plot the 17 global plots of sfo3max_avg
for k in sfo3max_avg:
cs = ax.contourf(lon[:], lat[:], k[:], cmap='magma')
plt.gcf().canvas.draw()
plt.pause(1) #control the interval between successive displays, currently set to 1 sec.
I'm making some plots of ocean temperature and salinity from data I have pulled from a NetCDF file. They are being used as frames in animations I'm making using the moviepy library. Packages I'm using:
from netCDF4 import Dataset # reads netCDF file
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap # basemap tools
from datetime import datetime, timedelta #for working with datetimes
import moviepy.editor as mpy # creates animation
from moviepy.video.io.bindings import mplfig_to_npimage # converts map to numpy array
from matplotlib.backends.backend_agg import FigureCanvasAgg # draws canvas so that map can be converted
When I draw the plots by themselves they look fine. Here's an exaple of the salinity map.
# map setup
fig = plt.figure()
fig.subplots_adjust(left=0., right=1., bottom=0., top=0.9)
# Setup the map
m = Basemap(projection='merc', llcrnrlat=-38.050653, urcrnrlat=-34.453367,\
llcrnrlon=147.996456, urcrnrlon=152.457344, lat_ts=20, resolution='h')
# draw stuff
m.drawcoastlines()
m.fillcontinents(color='black')
# plot salt
cs = m.pcolor(lons,lats,np.squeeze(salt), latlon = True ,vmin=salt_min, vmax=salt_max, cmap='viridis')
# plot colourbar
plt.colorbar()
# datetime title
plt.title('Regional - Salinity (PSU)\n' + frame_time.strftime("%Y-%m-%d %H:%M:%S") + ' | ' + str(fname) + '_idx: ' + str(frame_idx))
# stop axis from being cropped
plt.tight_layout()
However I want to plot temperature and salinity side by side but when I do the colour bars and figures don't draw as I would expect.
# set up figure
fig = plt.figure()
fig.subplots_adjust(left=0., right=1., bottom=0., top=0.9)
# Temperature figure
plt.subplot(1, 2, 1)
# Setup the map
m = Basemap(projection='merc', llcrnrlat=-38.050653, urcrnrlat=-34.453367,\
llcrnrlon=147.996456, urcrnrlon=152.457344, lat_ts=20, resolution='h')
# draw stuff
m.drawcoastlines()
m.fillcontinents(color='black')
# plot salt
cs = m.pcolor(lons,lats,np.squeeze(temp), latlon = True ,vmin=temp_min, vmax=temp_max, cmap='plasma')
# plot colourbar
plt.colorbar()
# datetime title
plt.title('Regional - Temperature (Celcius)\n' + frame_time.strftime("%Y-%m-%d %H:%M:%S") + ' | ' + str(fname) + '_idx: ' + str(frame_idx))
# Salinity figure
plt.subplot(1, 2, 2)
# Setup the map
m = Basemap(projection='merc', llcrnrlat=-38.050653, urcrnrlat=-34.453367,\
llcrnrlon=147.996456, urcrnrlon=152.457344, lat_ts=20, resolution='h')
# draw stuff
m.drawcoastlines()
m.fillcontinents(color='black')
# plot salt
cs = m.pcolor(lons,lats,np.squeeze(salt), latlon = True ,vmin=salt_min, vmax=salt_max, cmap='viridis')
# plot colourbar
plt.colorbar()
# datetime title
plt.title('Regional - Salinity (PSU)\n' + frame_time.strftime("%Y-%m-%d %H:%M:%S") + ' | ' + str(fname) + '_idx: ' + str(frame_idx))
# make layout nice
plt.tight_layout()
I'd like them to resemble the original example but side by side. I imagine I'm using the subplot function in correctly but I just can't work out how to fix it. Any ideas? Thanks.
NOTE: Maybe not essential for the question, but I might mention that each frame is being converted to a numpy array as I later pass the image to a function in moviepy to make the animation.
# convert to array
canvas = FigureCanvasAgg(fig)
canvas.draw()
frame = np.fromstring(canvas.tostring_rgb(), dtype='uint8')
frame = frame.reshape(fig.canvas.get_width_height()[::-1] + (3,))
Here's an example of the output:
I think this answer will help you: matplotlib colorbar in each subplot (possible duplicate)
It says to look at this matplotlib example page: https://matplotlib.org/examples/images_contours_and_fields/pcolormesh_levels.html
You can see that the example is specifying ax when using the colorbar-method:
fig.colorbar(im, ax=ax0)
Where ax-variables were assigned previously like so:
fig, (ax0, ax1) = plt.subplots(nrows=2)