Source code for argopy.plot.plot

#!/bin/env python
# -*coding: UTF-8 -*-
#
# We try to import dependencies and catch missing module errors in order to avoid to load argopy just because
# Matplotlib is not installed.
#
# Decorator warnUnless is mandatory
#
import warnings
import logging
import os
import json
from copy import copy

import xarray as xr
import pandas as pd
import numpy as np
from typing import Union
import importlib

from ..options import OPTIONS
from ..utils.loggers import warnUnless
from ..utils.checkers import check_wmo
from ..utils.geo import conv_lon
from ..utils.lists import subsample_list
from ..utils.casting import to_list
from ..errors import InvalidDatasetStructure

from .utils import STYLE, has_seaborn, has_mpl, has_cartopy, has_ipython, has_ipywidgets
from .utils import axes_style, latlongrid, land_feature
from .argo_colors import ArgoColors

if has_mpl:
    import matplotlib.pyplot as plt
    import matplotlib as mpl

if has_seaborn:
    # STYLE["axes"] = "dark"
    import seaborn as sns

if has_cartopy:
    import cartopy.crs as ccrs

if has_ipython:
    from IPython.display import Image, display

if has_ipywidgets:
    import ipywidgets


log = logging.getLogger("argopy.plot.plot")

path2assets = importlib.util.find_spec(
    "argopy.static.assets"
).submodule_search_locations[0]

with open(os.path.join(path2assets, "data_types.json"), "r") as f:
    DATA_TYPES = json.load(f)


def guess_cmap(hue: str) -> str | None:
    """Try to guess the ArgoColors colormap name to use as a function of the variable to plot

    If no custom colormap is identified, return None and let the caller decide on which one to use.

    """
    if hue.lower() in ArgoColors().list_valid_known_colormaps:
        cmap = hue.lower()
    elif "qc" in hue.lower():
        if "profile_" not in hue.lower():
            cmap = "qc"
        else:
            cmap = "pqc"
    elif "mode" in hue.lower():
        cmap = "data_mode"
    elif "status_code" in hue.lower():
        cmap = "deployment_status"
    else:
        cmap = None
    return cmap


[docs] def open_sat_altim_report( WMO: Union[str, list] = None, embed: Union[str, None] = "dropdown", **kwargs ): """Insert the CLS Satellite Altimeter Report figure in notebook cell This is the method called when using the facade fetcher methods ``plot``:: DataFetcher().float(6902745).plot('qc_altimetry') Parameters ---------- WMO: int or list The float WMO to display. By default, this is set to None and will insert the general dashboard. embed: str, default='dropdown' Set the embedding method. If set to None, simply return the list of urls to figures. Possible values are: ``dropdown``, ``slide`` and ``list``. Returns ------- list of Image with ``list`` embed or a dict with URLs Notes ----- Requires IPython to work as expected. If IPython is not available only URLs are returned. """ warnUnless( has_ipython, "requires IPython to work as expected, only URLs are returned otherwise", ) if "api_server" in kwargs: api_server = kwargs["api_server"] else: api_server = "https://data-argo.ifremer.fr" # Create the list of URLs and put them in a dictionary with WMO as keys: WMOs = check_wmo(WMO) urls = [] urls_dict = {} for this_wmo in WMOs: url = "%s/etc/argo-ast9-item13-AltimeterComparison/figures/%i.png" % ( api_server, this_wmo, ) log.debug(url) if has_ipython and embed == "list": urls.append(Image(url, embed=True)) else: urls.append(url) urls_dict[this_wmo] = url # Prepare rendering: if has_ipython and embed is not None: if has_ipywidgets and embed == "dropdown": def f(Float): return Image(url=urls_dict[int(Float)]) return ipywidgets.interact(f, Float=[str(wmo) for wmo in WMOs]) elif has_ipywidgets and embed == "slide": def f(Float): return Image(url=urls[Float]) return ipywidgets.interact( f, Float=ipywidgets.IntSlider(min=0, max=len(urls) - 1, step=1) ) elif embed == "list": return display(*urls) else: raise ValueError( "Invalid value for 'embed' argument. Must be: 'dropdown', 'slide', 'list' or None" ) else: return urls_dict
[docs] def plot_trajectory( df: pd.core.frame.DataFrame, style: str = STYLE["axes"], add_legend: bool = True, palette: str = STYLE["palette"], set_global: bool = False, with_cartopy: bool = has_cartopy, with_seaborn: bool = has_seaborn, **kwargs ): """Plot trajectories for an Argo index dataframe This function is called by the Data fetcher and :class:`ArgoIndex` plotting methods for trajectories: Examples -------- .. code-block:: python from argopy import DataFetcher, ArgoIndex obj = DataFetcher().float([6902766, 6902772, 6902914, 6902746]) fig, ax = obj.plot('trajectory') obj = ArgoIndex().query.wmo([6902766, 6902772, 6902914, 6902746]) fig, ax = obj.plot.trajectory() Parameters ---------- df: :class:`pandas.DataFrame` Input data with columns: 'wmo', 'longitude', 'latitude'. style: str Define the Seaborn axes style: 'white', 'darkgrid', 'whitegrid', 'dark', 'ticks'. add_legend: bool, default=True Add a box legend with list of floats. True by default for a maximum of 15 floats, otherwise no legend. palette: str Define colors to be used for floats: 'Set1' (default) or any other matplotlib colormap or name of a Seaborn palette (deep, muted, bright, pastel, dark, colorblind). set_global: bool, default=False Plot trajectories on a global world map or not. Returns ------- fig: :class:`matplotlib.figure.Figure` ax: :class:`matplotlib.axes.Axes` Warnings -------- This function will produce a plot even if `Cartopy <https://scitools.org.uk/cartopy/docs/latest/>`_ is not installed. If `Cartopy <https://scitools.org.uk/cartopy/docs/latest/>`_ is found, then this function will call :class:`argopy.plot.scatter_map`. """ warnUnless(has_mpl, "requires matplotlib installed") with axes_style(style): # Set up the figure and axis: defaults = {"figsize": (10, 6), "dpi": 90} if with_cartopy: opts = { **defaults, **{ "x": "longitude", "y": "latitude", "hue": "wmo", "traj": True, "legend": add_legend, "set_global": set_global, "cmap": palette, }, } opts = {**opts, **kwargs} fig, ax, hdl = scatter_map(df, **opts) return fig, ax else: opts = {**defaults, **kwargs} unvalid_keys = [] for key in opts.keys(): if key not in [ "nrows", "ncols", "sharex", "sharey", "squeeze", "width_ratios", "height_ratios", "subplot_kw", "gridspec_kw", "figsize", "dpi", ]: unvalid_keys.append(key) [opts.pop(key) for key in unvalid_keys] fig, ax = plt.subplots(**opts) # How many float in this dataset ? nfloat = len(df.groupby("wmo").first()) # Let's do the plot: if with_seaborn: mypal = sns.color_palette(palette, nfloat) sns.lineplot( x="longitude", y="latitude", hue="wmo", data=df, sort=False, palette=mypal, legend=False, ) sns.scatterplot( x="longitude", y="latitude", hue="wmo", data=df, palette=mypal ) else: mypal = ArgoColors(palette, N=nfloat).cmap for k, [name, group] in enumerate(df.groupby("wmo")): group.plot.line( x="longitude", y="latitude", ax=ax, color=mypal(k), legend=False, label="_nolegend_", ) group.plot.scatter( x="longitude", y="latitude", ax=ax, color=mypal(k), label=name ) if with_cartopy: if set_global: ax.set_global() latlongrid(ax, dx="auto", dy="auto", fontsize="auto") if not has_seaborn: ax.get_yaxis().set_visible(False) else: if set_global: if OPTIONS["longitude_convention"] == "360": ax.set_xlim(0, 360) else: # OPTIONS["longitude_convention"] == "180": ax.set_xlim(-180, 180) ax.set_ylim(-90, 90) ax.grid(visible=True, linewidth=1, color="gray", alpha=0.7, linestyle=":") if add_legend and nfloat <= 15: handles, labels = ax.get_legend_handles_labels() # if has_seaborn: # handles, labels = handles[1:], labels[1:] plt.legend( handles, labels, loc="upper right", bbox_to_anchor=(1.25, 1), title="Floats WMO", ) else: ax.get_legend().remove() return fig, ax
[docs] def bar_plot( df: pd.core.frame.DataFrame, by: str = "institution", style: str = STYLE["axes"], with_seaborn: bool = has_seaborn, **kwargs ): """Create a bar plot for an Argo index dataframe Pass a :class:`pandas.DataFrame` as returned by a :class:`argopy.DataFetcher.index` or :class:`argopy.ArgoIndex.to_dataframe` :: from argopy import DataFetcher df = DataFetcher(src='gdac').region([-80,-30,20,50,'2021-01','2021-08']).index bar_plot(df, by='profiler') Parameters ---------- df: :class:`pandas.DataFrame` As returned by an argopy index dataframe by: str, default='institution' The profile property to plot style: str, optional Define the Seaborn axes style: 'argopy', 'white', 'darkgrid', 'whitegrid', 'dark', 'ticks' Returns ------- fig: :class:`matplotlib.figure.Figure` ax: :class:`matplotlib.axes.Axes` """ warnUnless(has_mpl, "requires matplotlib installed") if by not in df: raise ValueError("'%s' is not a valid field for a bar plot" % by) with axes_style(style): defaults = {"figsize": (10, 6), "dpi": 90} fig, ax = plt.subplots(**{**defaults, **kwargs}) if with_seaborn: mind = df.groupby(by).size().sort_values(ascending=False).index sns.countplot(y=by, data=df, order=mind) else: df.groupby(by).size().sort_values(ascending=True).plot.barh(ax) ax.set_xlabel("Number of profiles") ax.set_ylabel("") return fig, ax
[docs] def scatter_map( # noqa: C901 data: Union[xr.Dataset, pd.core.frame.DataFrame], x: Union[str] = None, y: Union[str] = None, hue: Union[str] = None, markersize: int = 36, markeredgesize: float = 0.5, markeredgecolor: str = "default", cmap: Union[str] = None, traj: bool = True, traj_axis: Union[str] = None, traj_color: str = "default", legend: bool = True, legend_title: str = "default", legend_location: Union[str, int] = 0, cbar: bool = False, cbarlabels: Union[str, list] = "auto", cbarmaxlabels: int = 12, set_global: bool = False, padding: Union[str, list] = "auto", **kwargs ): """Try-to-be generic function to create a scatter plot on a map from **argopy** :class:`xarray.Dataset` or :class:`pandas.DataFrame` data Each point is an Argo profile location, colored with a user defined variable and colormap. Floats trajectory can be plotted or not. Note that all parameters have default values. Warnings -------- This function requires `Cartopy <https://scitools.org.uk/cartopy/docs/latest/>`_. Examples -------- :: from argopy.plot import scatter_map from argopy import DataFetcher ArgoSet = DataFetcher(mode='expert').float([6902771, 4903348]).load() ds = ArgoSet.data.argo.point2profile() df = ArgoSet.index scatter_map(df) scatter_map(ds) scatter_map(ds, hue='DATA_MODE') scatter_map(ds, hue='PSAL_QC') :: from argopy import OceanOPSDeployments df = OceanOPSDeployments([-90, 0, 0, 90]).to_dataframe() scatter_map(df, hue='status_code', traj=False) scatter_map(df, x='lon', y='lat', hue='status_code', traj=False, cmap='deployment_status') Parameters ---------- data: :class:`xarray.Dataset` or :class:`pandas.DataFrame` Input data structure x: str, default=None Name of the data variable to use as longitude. If x is set to None, we'll try to guess which variable to use among standard names. y: str, default=None Name of the data variable to use as latitude. If y is set to None, we'll try to guess which variable to use among standard names. hue: str, default=None Name of the data variable to use for points coloring. If hue is set to None, we'll try to guess which variable to use to color points according to WMO. Returns ------- fig: :class:`matplotlib.figure.Figure` ax: :class:`matplotlib.axes.Axes` patches: Dict with ax collections Other Parameters ---------------- markersize: int, default=36 Size of the marker used for profiles location. markeredgesize: float, default=0.5 Size of the marker edge used for profiles location. markeredgecolor: str, default='default' Color to use for the markers edge. The default color is 'DARKBLUE' from :class:`argopy.plot.ArgoColors.COLORS` cmap: str, default=None Colormap to use for points coloring. If set to None, we'll try to guess the most appropriate colormap for the ``hue`` argument by matching it to values in :class:`argopy.plot.ArgoColors.list_valid_known_colormaps`. traj: bool, default=True Set to True in order to plot each float trajectories, i.e. join with a line all profiles from a single platform. traj_axis: str, default='wmo' Name of the data variable to use in order to determine profiles group making a single trajectory. traj_color: str, default='default' The unique color to use for all trajectories. The default color is the ``markeredgecolor`` value. legend: bool, default=True Display or not a legend for hue colors meaning. If the legend is too large, it can be removed with ``ax.get_legend().remove()``, or you may use the colorbar instead. legend_title: str, default='default' String title of the legend box. By default, it is set to the ``hue`` value. legend_location: str, default='upper right' Location of the legend box. This is passed to the ``loc`` argument of :class:`~matplotlib:matplotlib.legend.Legend`. cbar: bool, default=False Display or not a colorbar for hue colors. cbarlabels: list[str], default="auto" Possibly customize the list of colorbar labels or let it be determined automatically. cbarmaxlabels: int, default=12 Maximum number of ticks and labels on the colorbar. set_global: bool, default=False Force the map to be global. padding: str, list, default='auto' Additional space to the map around data points. If not set to 'auto', this argument must be a list: - of 2 values for longitude and latitude padding - of 4 values for west, east, south and north padding kwargs All other arguments are passed to :class:`matplotlib.figure.Figure.subplots` """ warnUnless(has_mpl and has_cartopy, "requires matplotlib AND cartopy installed") if isinstance(data, xr.Dataset) and data.argo._type == "point": # data = data.argo.point2profile(drop=True) raise InvalidDatasetStructure( "Function only available for a collection of profiles" ) # Try to guess the default hue, i.e. name for WMO: def guess_trajvar(data): for v in ["WMO", "PLATFORM_NUMBER"]: if v.lower() in data: return v.lower() if v.upper() in data: return v.upper() raise ValueError( "Can't guess the variable name for default hue/trajectory grouping (WMO)" ) hue = guess_trajvar(data) if hue is None else hue if isinstance(data, xr.Dataset) and data.argo.N_LEVELS > 1 and 'N_LEVELS' in data[hue].dims: warnings.warn( f"More than one N_LEVELS found in this dataset for '{hue}', scatter_map will use the first level only" ) data = data.isel(N_LEVELS=0) cmap = cmap or guess_cmap(hue) or STYLE["palette"] # Try to guess the x and y variables: def guess_xvar(data): for v in ["lon", "long", "longitude", "x"]: if v.lower() in data: return v.lower() if v.upper() in data: return v.upper() if isinstance(data, xr.Dataset): for v in data.coords: if ( "_CoordinateAxisType" in data[v].attrs and data[v].attrs["_CoordinateAxisType"] == "Lon" ): return v if "axis" in data[v].attrs and data[v].attrs["axis"] == "X": return v raise ValueError("Can't guess the variable name for longitudes") def guess_yvar(data): for v in ["lat", "lati", "latitude", "y"]: if v.lower() in data: return v.lower() if v.upper() in data: return v.upper() if isinstance(data, xr.Dataset): for v in data.coords: if ( "_CoordinateAxisType" in data[v].attrs and data[v].attrs["_CoordinateAxisType"] == "Lat" ): return v if "axis" in data[v].attrs and data[v].attrs["axis"] == "Y": return v raise ValueError("Can't guess the variable name for latitudes") x = guess_xvar(data) if x is None else x y = guess_yvar(data) if y is None else y # Adjust legend title: if legend_title == "default": legend_title = str(hue) # Load Argo colors: nHue = ( len(data.groupby(hue).first()) if isinstance(data, pd.DataFrame) else len(data.groupby(hue)) ) mycolors = ArgoColors(cmap, nHue) COLORS = mycolors.COLORS if markeredgecolor == "default": markeredgecolor = COLORS["DARKBLUE"] if traj_color == "default": traj_color = markeredgecolor # Try to guess the trajectory grouping variable, i.e. name for WMO traj_axis = guess_trajvar(data) if traj and traj_axis is None else traj_axis # Set up the figure and axis: defaults = {"figsize": (10, 6), "dpi": 90} if OPTIONS["longitude_convention"] == "180": central_longitude = 0.0 else: # OPTIONS['longitude_convention'] == '360': central_longitude = 180.0 subplot_kw = {"projection": ccrs.PlateCarree(central_longitude=central_longitude)} fig, ax = plt.subplots(**{**defaults, **kwargs}, subplot_kw=subplot_kw) ax.add_feature( land_feature, color=COLORS["BLUE"], edgecolor=COLORS["CYAN"], linewidth=0.1, alpha=0.3, ) patches = [] scatter_legend_labels = [] for k, [name, group] in enumerate(data.groupby(hue)): if mycolors.registered and name not in mycolors.lookup: log.info( "Found '%s' values not available in the '%s' colormap" % (name, mycolors.definition["name"]) ) else: scatter_opts = { "color": ( mycolors.lookup[name] if mycolors.registered else mycolors.cmap(k) ), "label": ( "%s: %s" % (name, mycolors.ticklabels[name]) if mycolors.registered else name ), "zorder": 10, "sizes": [markersize], "edgecolor": markeredgecolor, "linewidths": markeredgesize, "transform": ccrs.PlateCarree(), } if isinstance(data, pd.DataFrame) and not legend: scatter_opts["legend"] = ( False # otherwise Pandas will add a legend even if we set legend=False ) sc = group.plot.scatter(x=x, y=y, ax=ax, **scatter_opts) patches.append(sc) scatter_legend_labels.append(scatter_opts["label"]) if cbar: if isinstance(cbarlabels, str) and cbarlabels == "auto": # handles, cbarlabels = ax.get_legend_handles_labels() cbarlabels = scatter_legend_labels.copy() cbar_handle = mycolors.cbar( ticklabels=cbarlabels, ax=ax, fraction=0.03, label=legend_title ) ticks = cbar_handle.get_ticks() if cbarmaxlabels is not None: new_ticks = [ticks[0]] [new_ticks.append(v) for v in subsample_list(ticks, cbarmaxlabels - 2)] new_ticks.append(ticks[-1]) new_cbarlabels = [cbarlabels[0]] [ new_cbarlabels.append(v) for v in subsample_list(cbarlabels, cbarmaxlabels - 2) ] new_cbarlabels.append(cbarlabels[-1]) cbar_handle.set_ticks(subsample_list(ticks, cbarmaxlabels)) cbar_handle.set_ticklabels(subsample_list(cbarlabels, cbarmaxlabels)) else: cbar_handle = None if traj: for k, [_, group] in enumerate(data.groupby(traj_axis)): traj_handle = ax.plot( group[x], group[y], color=traj_color, linewidth=0.5, label="_nolegend_", zorder=2, transform=ccrs.Geodetic(), # do not use PlateCarree here, Geodetic allows smooth traj across 0 & 180 ) else: traj_handle = None if set_global: ax.set_global() else: lon = conv_lon(data[x], OPTIONS["longitude_convention"]) lat = data[y] extent = [np.min(lon), np.max(lon), np.min(lat), np.max(lat)] rge = [np.abs(np.max(lon) - np.min(lon)), np.abs(np.max(lat) - np.min(lat))] if padding == "auto": padding = [-rge[0] / 10, rge[0] / 10, -rge[1] / 10, rge[1] / 10] else: padding = to_list(padding) if len(padding) == 1: padding = [-padding[0], padding[0], -padding[0], padding[0]] elif len(padding) == 2: padding = [-padding[0], padding[0], -padding[1], padding[1]] elif len(padding) != 4: raise ValueError("'padding' must be 'auto', a list of 1, 2 or 4 values") extent[0] = extent[0] + padding[0] extent[1] = extent[1] + padding[1] extent[2] = extent[2] + padding[2] extent[3] = extent[3] + padding[3] ax.set_extent(extent) latlongrid( ax, dx="auto", dy="auto", label_style_arg={"color": COLORS["BLUE"], "fontsize": 10}, **{"color": COLORS["BLUE"], "alpha": 0.7} ) ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) if legend: handles, labels = ax.get_legend_handles_labels() legend_handle = plt.legend( handles, labels, loc=legend_location, bbox_to_anchor=(1.26, 1), title=legend_title, ) else: legend_handle = None for spine in ax.spines.values(): spine.set_edgecolor(COLORS["DARKBLUE"]) ax.set_title("") handles = { "scatter": patches, "cbar": cbar_handle, "legend": legend_handle, "traj": traj_handle, "ArgoColors": mycolors, } return fig, ax, handles
[docs] def scatter_plot( ds : xr.Dataset, param : str, x : str = "TIME", y : str = "PRES", figsize : tuple =(18, 6), cmap : 'str | mpl.colors.Colormap | ArgoColors | None' = None, vmin : int | float | None = None, vmax : int | float | None = None, s : int = 4, cbar: bool = False, style: str = STYLE["axes"], **kwargs, ): """Argo parameter scatter plot Parameters ---------- cmap : str or `ArgoColors` or `~matplotlib.colors.Colormap` or None The Colormap instance or registered colormap name used to map scalar data to colors. """ warnUnless(has_mpl, "requires matplotlib installed") #deprecation if 'this_param' in kwargs: warnings.warn( f"The argument 'this_param' is deprecated since version 1.4.0. Please update your code to use 'param' instead.", category=DeprecationWarning, stacklevel=2, ) param = kwargs['this_param'] # Safe fallback on new argument if 'this_x' in kwargs: warnings.warn( f"The argument 'this_x' is deprecated since version 1.4.0. Please update your code to use 'x' instead.", category=DeprecationWarning, stacklevel=2, ) x = kwargs['this_x'] # Safe fallback on new argument if 'this_y' in kwargs: warnings.warn( f"The argument 'this_y' is deprecated since version 1.4.0. Please update your code to use 'y' instead.", category=DeprecationWarning, stacklevel=2, ) y = kwargs['this_y'] # Safe fallback on new argument if param in DATA_TYPES["data"]["str"]: raise ValueError("scatter_plot does not support parameter of string type (yet !)") # Transform the 'cmap' argument into a mpl.colors.Colormap instance a_color = None if cmap is None: cmap = guess_cmap(param) if cmap is not None: a_color = ArgoColors(cmap, N=kwargs.get('N', None)) cmap: 'mpl.colors.Colormap' = a_color.cmap else: a_color = ArgoColors('gist_ncar', N=kwargs.get('N', None)) cmap: 'mpl.colors.Colormap' = a_color.cmap elif isinstance(cmap, str): a_color = ArgoColors(cmap, N=kwargs.get('N', None)) cmap: 'mpl.colors.Colormap' = a_color.cmap elif isinstance(cmap, ArgoColors): a_color : ArgoColors = copy(cmap) cmap: 'mpl.colors.Colormap' = a_color.cmap if has_mpl and not isinstance(cmap, mpl.colors.Colormap): raise ValueError(f"'cmap' argument must be a str or `ArgoColors` or `~matplotlib.colors.Colormap` or None. Got '{type(cmap)}' instead.") cbticklabels = 'auto' if a_color and a_color.registered: cbticklabels = a_color.ticklabels vmin, vmax = a_color.definition['ticks'][0], a_color.definition['ticks'][-1]+1 def get_vlabel(this_v): attrs = this_v.attrs if "standard_name" in attrs: name = attrs["standard_name"] elif "long_name" in attrs: name = attrs["long_name"] else: name = this_v units = attrs["units"] if "units" in attrs else None return "%s\n[%s]" % (name, units) if units else name # Read/reshape variables for the plot: x_da, y_da = ds[x], ds[y] if "INTERPOLATED" in y: # "PRES_INTERPOLATED" or "PRES_ADJUSTED_INTERPOLATED" # We'll be using pcolormesh instead of scatter x_bounds, y_bounds = np.meshgrid(x_da, y_da, indexing="ij") c_da = ds[param] # Possibly broadcast x_da, y_da on c dimensions: if not x_da.shape == y_da.shape or not x_da.shape == c_da.shape or not y_da.shape == c_da.shape: x_da = x_da.broadcast_like(c_da) y_da = y_da.broadcast_like(c_da) if x_da.shape == y_da.shape and not y_da.shape == c_da.shape: c_da = c_da.broadcast_like(x_da) assert x_da.shape == y_da.shape assert y_da.shape == c_da.shape # with axes_style(style): fig, ax = plt.subplots(dpi=90, figsize=figsize) if vmin == "attrs": vmin = c_da.attrs["valid_min"] if "valid_min" in c_da.attrs else None if vmin is None: vmin = np.nanpercentile(c_da, 10) if vmax == "attrs": vmax = c_da.attrs["valid_max"] if "valid_max" in c_da.attrs else None if vmax is None: vmax = np.nanpercentile(c_da, 90) if "INTERPOLATED" in y: m = ax.pcolormesh(x_bounds, y_bounds, c_da, cmap=cmap, vmin=vmin, vmax=vmax) else: m = ax.scatter(x_da, y_da, c=c_da, cmap=cmap, s=s, vmin=vmin, vmax=vmax) if cbar: cbar = fig.colorbar(m, shrink=0.9, ax=ax) cbar.ax.set_ylabel(get_vlabel(c_da), rotation=90) if isinstance(cbticklabels, dict): cbar.set_ticks(to_list([k + 0.5 for k in cbticklabels.keys()])) cbar.set_ticklabels(to_list([k for k in cbticklabels.values()])) ylim = ax.get_ylim() if "PRES" in y: ax.invert_yaxis() y_bottom, y_top = np.max(ylim), np.min(ylim) else: y_bottom, y_top = ylim if x == "CYCLE_NUMBER": ax.set_xlim([np.min(x_da) - 1, np.max(x_da) + 1]) elif x == "TIME": ax.set_xlim([np.min(x_da), np.max(x_da)]) if "PRES" in y: ax.set_ylim([y_bottom, 0]) # ax.set_xlabel(get_vlabel(x_da)) ax.set_ylabel(get_vlabel(y_da)) if cbar: return fig, ax, m, cbar else: return fig, ax, m