Download utils/plotting.py from charlie-becker/CAMulator: direct link, hf CLI and curl.
- Browser
- Download file 4.04 kB
-
https://huggingface.co/charlie-becker/CAMulator/resolve/main/utils/plotting.py
- Command line
-
hf download hf://charlie-becker/CAMulator/utils/plotting.py
-
curl -L -o plotting.py https://huggingface.co/charlie-becker/CAMulator/resolve/main/utils/plotting.py
4.04 kB
| """Small plotting helpers for the CAMulator timescales tutorial.""" | |
| import cartopy.crs as ccrs | |
| import numpy as np | |
| def map_axis(fig, nrows, ncols, index, region=None, extent=None, central_longitude=180): | |
| """Add a map axis with coastlines. region = {'lat': (min, max), 'lon': (min, max)}, longitudes in degrees east | |
| (negative = west; lon min > max crosses the date line). (The older extent = (lon_min, lon_max, lat_min, lat_max) in | |
| 0-360 also works.)""" | |
| if region is not None: | |
| lo0, lo1 = region["lon"] | |
| if lo1 < lo0: | |
| lo1 += 360 | |
| extent = (lo0, lo1, *region["lat"]) | |
| central_longitude = (lo0 + lo1) / 2 | |
| ax = fig.add_subplot(nrows, ncols, index, projection=ccrs.PlateCarree(central_longitude=central_longitude)) | |
| if extent is not None: | |
| ax.set_extent(extent, crs=ccrs.PlateCarree()) | |
| ax.coastlines(linewidth=0.6) | |
| return ax | |
| DATA_CRS = ccrs.PlateCarree() # pass as transform= when plotting lat/lon data on a map axis | |
| def sinlat_axis(ax, ticks=(-60, -30, -10, 0, 10, 30, 60)): | |
| """Label an x axis that holds sin(latitude) with latitudes, so each band takes the room its area does.""" | |
| ax.set_xticks(np.sin(np.deg2rad(ticks))) | |
| ax.set_xticklabels([f'{abs(t)}°{"S" if t < 0 else "N" if t > 0 else ""}' for t in ticks]) | |
| ax.set_xlabel("Latitude (spaced by area)") | |
| def sinlat(lat): | |
| return np.sin(np.deg2rad(lat)) | |
| def polar_axis(fig, nrows, ncols, index, lat_min=50): | |
| """Add a north-polar stereographic map axis with coastlines, showing latitudes north of lat_min.""" | |
| ax = fig.add_subplot(nrows, ncols, index, projection=ccrs.NorthPolarStereo()) | |
| ax.set_extent([-180, 180, lat_min, 90], crs=ccrs.PlateCarree()) | |
| ax.coastlines(linewidth=0.6) | |
| return ax | |
| # Plain-language names, units and reading notes for variables participants can choose (everyday units, see | |
| # core.EVERYDAY_UNITS). | |
| VARIABLES = { | |
| "FLUT": ("OLR", "W/m²", "outgoing longwave radiation; lower = more deep cloud"), | |
| "PRECT": ("Rain", "mm/day", None), | |
| "TREFHT": ("2-m temperature", "K", None), | |
| "TS": ("Surface temperature", "K", None), | |
| "PS": ("Surface pressure", "hPa", None), | |
| "U10": ("10-m wind speed", "m/s", None), | |
| "westerly_stress": ("Surface wind stress", "mN/m²", "positive = westerly"), | |
| "rain": ("Rain", "mm/day", None), | |
| "T": ("2-m temperature", "K", None), | |
| "vapour": ("Column water vapour", "mm", None), | |
| } | |
| def name(variable): | |
| """Short display name, e.g. 'OLR', 'Rain'.""" | |
| return VARIABLES[variable][0] if variable in VARIABLES else variable | |
| def units(variable): | |
| return VARIABLES[variable][1] if variable in VARIABLES else "" | |
| def label(variable): | |
| """Axis / colour-bar label: name, units and a reading note, e.g. 'OLR (W/m²; ... lower = more deep cloud)'.""" | |
| if variable not in VARIABLES: | |
| return variable | |
| short, unit, note = VARIABLES[variable] | |
| return f"{short} ({unit}; {note})" if note else f"{short} ({unit})" | |
| def symmetric_limit(field, quantile=0.98): | |
| """A colour limit that covers almost all of a field's values, the same on both sides of zero.""" | |
| return float(np.nanquantile(np.abs(np.asarray(field)), quantile)) or 1.0 | |
| def km_axis(ax, circumference_km): | |
| """Add a top x axis showing the wavelength in km for an axis of zonal wavenumbers.""" | |
| top = ax.secondary_xaxis("top", functions=(lambda k: circumference_km / np.maximum(k, 1e-9), | |
| lambda km: circumference_km / np.maximum(km, 1e-9))) | |
| top.set_xlabel("Wavelength (km)") | |
| return top | |
| def region_text(region): | |
| """{'lat': (-5, 5), 'lon': (35, 45)} -> '5°S-5°N, 35-45°E' (for titles).""" | |
| def lat(v): | |
| return f"{abs(v):g}°{'S' if v < 0 else 'N'}" if v else "0°" | |
| def lon(v): | |
| return f"{abs(v):g}°{'W' if v < 0 else 'E'}" if v else "0°" | |
| la0, la1 = region["lat"] | |
| text = f"{lat(la0)}-{lat(la1)}" | |
| if "lon" in region: | |
| text += f", {lon(region['lon'][0])}-{lon(region['lon'][1])}" | |
| return text | |