Skip to content

Plotting#

For a worked, example-driven walkthrough see the Plotting NetCDF data tutorial. This page is the API reference (signature table + the auto-generated Selectors / ColourOpts / FacetSpec docs).

NetCDF.plot has its own, xarray-aligned plotting surface — it does not inherit the GeoTIFF / Sentinel-imagery semantics of Dataset.plot. You pick a variable, slice along the non-spatial dimensions, and pass colour / faceting / coordinate options through small grouped, frozen dataclasses (Selectors, ColourOpts, FacetSpec) re-exported from pyramids.netcdf.

The three option bags feed NetCDF.plot, which is a thin facade over NetCDFPlot (_plot.py). That resolves the variable/slice and hands the array to the shared render_array core (_plot_helpers.py) — the same renderer behind Dataset.plot and DatasetCollection.plot — which draws into a cleopatra ArrayGlyph and returns the glyph (.fig / .ax / .im):

Hold "Ctrl" to enable pan & zoom
flowchart LR
    SEL["Selectors<br/>time · level · member<br/>sel · isel"]
    COL["ColorOpts / ColourOpts<br/>cmap · vmin · vmax · robust<br/>levels · norm · center · extend"]
    FAC["FacetSpec<br/>col · row · col_wrap"]
    SEL --> P["NetCDF.plot(variable, ...)"]
    COL --> P
    FAC --> P
    P --> NP["NetCDFPlot<br/>(_plot.py)"]
    NP --> RA["render_array<br/>(_plot_helpers.py)"]
    RA --> CG(["cleopatra ArrayGlyph"])
    CG --> FIG[("glyph<br/>.fig · .ax · .im")]

ColorOpts is the canonical name; ColourOpts is a deprecated British-spelling alias that warns on construction but compares/hashes equal to the corresponding ColorOpts.

from pyramids.netcdf import NetCDF, Selectors, ColourOpts, FacetSpec

nc = NetCDF.read_file("era5.nc")

# pick a variable, select along non-spatial dims, xarray-style colour kwargs
nc.plot("t2m", selectors=Selectors(time="2020-01-01", level=850),
        colour=ColourOpts(cmap="coolwarm", robust=True))

# curvilinear (WRF) grid -> pcolormesh, faceted over time
nc.plot("T2", coords=(XLONG, XLAT), kind="pcolormesh",
        facet=FacetSpec(col="time", col_wrap=4))

# animate over a dimension with lazy, per-frame reads ([lazy] extra)
nc.plot("t2m", animate="time", chunks={"time": 1})

Signature#

NetCDF.plot(variable=None, *, selectors=None, colour=None, facet=None, coords=None, kind="auto", animate=None, chunks=None, basemap=None, exclude_value=None, title=None, ax=None, figsize=None, **kwargs)

Parameter Type Notes
variable str, optional Variable to plot; defaults to the dataset's single / active variable.
selectors Selectors, optional Slice along non-spatial dimensions — time=, level=, member=, plus generic sel= / isel=.
colour ColourOpts, optional Colour mapping — cmap, vmin, vmax, robust, levels, norm, center, extend, add_colorbar, cbar_kwargs.
facet FacetSpec, optional Small-multiples grid — col=, row=, col_wrap=.
coords tuple[ndarray, ndarray], optional Curvilinear (x_2d, y_2d) coordinates -> pcolormesh. Auto-detected from CF coordinates / WRF / ROMS / NEMO conventions when omitted.
kind str, optional "auto", "imshow", "pcolormesh", "contour", "contourf".
animate bool or str, optional Animate over a dimension (its name, or True for the leading non-spatial dimension).
chunks dict, optional Dask chunking — switches to a lazy read; only the rendered slice / frame is materialised. Requires the [lazy] extra.
basemap bool or str, optional Overlay a web-tile basemap (provider name as a string, e.g. "CartoDB.Positron"). Requires the [viz] extra.
**kwargs Forwarded to cleopatra's ArrayGlyph for figure / colour-bar styling, color_scale, etc.

The GeoTIFF-only kwargs band, rgb, surface_reflectance, cutoff, percentile, overview, and overview_index are not accepted on NetCDF.plot — passing any of them raises TypeError with a hint pointing at the replacement above (use selectors= to pick a slice, colour= for the colour scale, and so on).

Internally NetCDF.plot is a thin facade over pyramids.netcdf._plot.NetCDFPlot, which shares the pyramids.dataset._plot_helpers.render_array rendering core with Dataset.plot and DatasetCollection.plot (and mesh_render with UgridDataset.plot). The full rendered method signature and docstring are on the NetCDF Class reference page.

Option dataclasses#

pyramids.netcdf.Selectors dataclass #

Dimension selectors for :meth:NetCDF.plot.

Groups the label-based dimension selectors that pin a multi-dim NetCDF variable to a single 2-D slice. All fields are optional; pass only the dims that need pinning. Convenience aliases (time / level / member) auto-detect the matching band dim name; raw sel / isel dicts take the dim name verbatim.

Attributes:

Name Type Description
time Any

Convenience label selector for the time dim. Equivalent to sel={<time-dim-name>: time}. Defaults to None.

level Any

Convenience label selector for the vertical dim (auto-detected as the first of pressure_level / depth / height / z present on the variable's band dims). Defaults to None.

member Any

Convenience label selector for the ensemble dim (member / realization / ensemble). Defaults to None.

sel dict[str, Any] | None

Raw label selectors forwarded directly to :meth:NetCDF.sel. Keys must be valid band-dim names of the variable. Defaults to None.

isel dict[str, int] | None

Positional selectors keyed by dim name. Each int is converted to the corresponding coord value via the variable's band-dim coord map; dims without coord values receive the int unchanged. Defaults to None.

Examples:

  • The default constructor produces an all-None instance that is safe to forward to plot unchanged:

    >>> from pyramids.netcdf.plot_options import Selectors
    >>> empty = Selectors()
    >>> empty.time is None
    True
    >>> empty.sel is None
    True
    
  • Pin both the time and pressure-level dims of a 4-D variable:

    >>> from pyramids.netcdf.plot_options import Selectors
    >>> sel = Selectors(time=12, level=500)
    >>> sel.time
    12
    >>> sel.level
    500
    
  • Frozen instances reject attribute assignment so the option bag stays stable after construction:

    >>> from dataclasses import FrozenInstanceError
    >>> from pyramids.netcdf.plot_options import Selectors
    >>> sel = Selectors(time=0)
    >>> try:
    ...     sel.time = 1
    ... except FrozenInstanceError:
    ...     print("frozen")
    frozen
    
Source code in src/pyramids/netcdf/plot_options.py
@dataclass(frozen=True)
class Selectors:
    """Dimension selectors for :meth:`NetCDF.plot`.

    Groups the label-based dimension selectors that pin a multi-dim
    NetCDF variable to a single 2-D slice. All fields are optional;
    pass only the dims that need pinning. Convenience aliases
    (``time`` / ``level`` / ``member``) auto-detect the matching band
    dim name; raw ``sel`` / ``isel`` dicts take the dim name verbatim.

    Attributes:
        time: Convenience label selector for the time dim. Equivalent
            to ``sel={<time-dim-name>: time}``. Defaults to None.
        level: Convenience label selector for the vertical dim
            (auto-detected as the first of
            ``pressure_level`` / ``depth`` / ``height`` / ``z`` present
            on the variable's band dims). Defaults to None.
        member: Convenience label selector for the ensemble dim
            (``member`` / ``realization`` / ``ensemble``). Defaults to
            None.
        sel: Raw label selectors forwarded directly to
            :meth:`NetCDF.sel`. Keys must be valid band-dim names of
            the variable. Defaults to None.
        isel: Positional selectors keyed by dim name. Each int is
            converted to the corresponding coord value via the
            variable's band-dim coord map; dims without coord values
            receive the int unchanged. Defaults to None.

    Examples:
        - The default constructor produces an all-``None`` instance
          that is safe to forward to ``plot`` unchanged:

            ```python
            >>> from pyramids.netcdf.plot_options import Selectors
            >>> empty = Selectors()
            >>> empty.time is None
            True
            >>> empty.sel is None
            True

            ```

        - Pin both the time and pressure-level dims of a 4-D variable:

            ```python
            >>> from pyramids.netcdf.plot_options import Selectors
            >>> sel = Selectors(time=12, level=500)
            >>> sel.time
            12
            >>> sel.level
            500

            ```

        - Frozen instances reject attribute assignment so the option
          bag stays stable after construction:

            ```python
            >>> from dataclasses import FrozenInstanceError
            >>> from pyramids.netcdf.plot_options import Selectors
            >>> sel = Selectors(time=0)
            >>> try:
            ...     sel.time = 1
            ... except FrozenInstanceError:
            ...     print("frozen")
            frozen

            ```
    """

    time: Any = None
    level: Any = None
    member: Any = None
    sel: dict[str, Any] | None = None
    isel: dict[str, int] | None = None

pyramids.netcdf.ColourOpts dataclass #

Bases: ColorOpts

Deprecated British-spelling alias for :class:ColorOpts.

Retained for backward compatibility; instantiating it emits a :class:DeprecationWarning. Use :class:ColorOpts instead — it matches the color_scale / cmap spelling used elsewhere in the API. The alias is a subclass, so existing isinstance(x, ColourOpts) checks and any code passing a ColourOpts to NetCDF.plot keep working.

Examples:

  • Constructing it warns but otherwise behaves exactly like ColorOpts:

    >>> import warnings
    >>> from pyramids.netcdf.plot_options import ColourOpts, ColorOpts
    >>> with warnings.catch_warnings():
    ...     warnings.simplefilter("ignore")
    ...     opts = ColourOpts(cmap="viridis")
    >>> opts.cmap
    'viridis'
    >>> isinstance(opts, ColorOpts)
    True
    
    - It compares equal, by value, to the same ColorOpts (back-compat):

    >>> import warnings
    >>> from pyramids.netcdf.plot_options import ColourOpts, ColorOpts
    >>> with warnings.catch_warnings():
    ...     warnings.simplefilter("ignore")
    ...     ColourOpts(cmap="viridis") == ColorOpts(cmap="viridis")
    True
    
Source code in src/pyramids/netcdf/plot_options.py
@dataclass(frozen=True)
class ColourOpts(ColorOpts):
    """Deprecated British-spelling alias for :class:`ColorOpts`.

    Retained for backward compatibility; instantiating it emits a
    :class:`DeprecationWarning`. Use :class:`ColorOpts` instead — it matches the
    ``color_scale`` / ``cmap`` spelling used elsewhere in the API. The alias is a
    subclass, so existing ``isinstance(x, ColourOpts)`` checks and any code passing a
    ``ColourOpts`` to ``NetCDF.plot`` keep working.

    Examples:
        - Constructing it warns but otherwise behaves exactly like ``ColorOpts``:

            ```python
            >>> import warnings
            >>> from pyramids.netcdf.plot_options import ColourOpts, ColorOpts
            >>> with warnings.catch_warnings():
            ...     warnings.simplefilter("ignore")
            ...     opts = ColourOpts(cmap="viridis")
            >>> opts.cmap
            'viridis'
            >>> isinstance(opts, ColorOpts)
            True

            ```
        - It compares equal, by value, to the same ``ColorOpts`` (back-compat):

            ```python
            >>> import warnings
            >>> from pyramids.netcdf.plot_options import ColourOpts, ColorOpts
            >>> with warnings.catch_warnings():
            ...     warnings.simplefilter("ignore")
            ...     ColourOpts(cmap="viridis") == ColorOpts(cmap="viridis")
            True

            ```
    """

    def __post_init__(self) -> None:
        """Emit a deprecation warning; the dataclass fields are already set."""
        warnings.warn(
            "ColourOpts is deprecated; use ColorOpts instead "
            "(same fields, US spelling).",
            DeprecationWarning,
            stacklevel=2,
        )

    def __eq__(self, other: object) -> bool:
        """Compare by field value against any ``ColorOpts`` (back-compat with the pre-split class).

        Before ``ColourOpts`` became a subclass it *was* ``ColorOpts``, so equal field values
        compared equal. The dataclass-generated ``__eq__`` enforces an exact class match, which
        would silently make ``ColourOpts(cmap="x") == ColorOpts(cmap="x")`` False. Compare on the
        field tuple instead so value-equality is preserved in both directions (``ColorOpts.__eq__``
        returns ``NotImplemented`` for the cross-class case, so Python defers to this method).
        """
        if isinstance(other, ColorOpts):
            return astuple(self) == astuple(other)
        return NotImplemented

    # A dataclass that defines __eq__ loses the auto-generated __hash__; restore the
    # frozen field-based hash (class-independent, so it matches an equal ColorOpts).
    __hash__ = ColorOpts.__hash__

__eq__(other) #

Compare by field value against any ColorOpts (back-compat with the pre-split class).

Before ColourOpts became a subclass it was ColorOpts, so equal field values compared equal. The dataclass-generated __eq__ enforces an exact class match, which would silently make ColourOpts(cmap="x") == ColorOpts(cmap="x") False. Compare on the field tuple instead so value-equality is preserved in both directions (ColorOpts.__eq__ returns NotImplemented for the cross-class case, so Python defers to this method).

Source code in src/pyramids/netcdf/plot_options.py
def __eq__(self, other: object) -> bool:
    """Compare by field value against any ``ColorOpts`` (back-compat with the pre-split class).

    Before ``ColourOpts`` became a subclass it *was* ``ColorOpts``, so equal field values
    compared equal. The dataclass-generated ``__eq__`` enforces an exact class match, which
    would silently make ``ColourOpts(cmap="x") == ColorOpts(cmap="x")`` False. Compare on the
    field tuple instead so value-equality is preserved in both directions (``ColorOpts.__eq__``
    returns ``NotImplemented`` for the cross-class case, so Python defers to this method).
    """
    if isinstance(other, ColorOpts):
        return astuple(self) == astuple(other)
    return NotImplemented

__post_init__() #

Emit a deprecation warning; the dataclass fields are already set.

Source code in src/pyramids/netcdf/plot_options.py
def __post_init__(self) -> None:
    """Emit a deprecation warning; the dataclass fields are already set."""
    warnings.warn(
        "ColourOpts is deprecated; use ColorOpts instead "
        "(same fields, US spelling).",
        DeprecationWarning,
        stacklevel=2,
    )

pyramids.netcdf.FacetSpec dataclass #

Faceting specification for :meth:NetCDF.plot.

When set, NetCDF.plot builds a stack of slices along the named dims and hands them to :meth:cleopatra.array_glyph.ArrayGlyph.facet. At least one of col or row must be set; row alone (without col) is invalid and rejected by the validator.

Attributes:

Name Type Description
col str | None

Band-dim name to facet across columns. Defaults to None.

row str | None

Band-dim name to facet across rows. Requires col. Defaults to None.

col_wrap int | None

When only col is set, wrap into this many columns (so N panels lay out as ceil(N/col_wrap) x col_wrap). Ignored when row is set. Defaults to None.

Examples:

  • A column-only facet over the time dim:

    >>> from pyramids.netcdf.plot_options import FacetSpec
    >>> spec = FacetSpec(col="time")
    >>> spec.col
    'time'
    >>> spec.row is None
    True
    
  • Two-axis facet across time (columns) and pressure level (rows):

    >>> from pyramids.netcdf.plot_options import FacetSpec
    >>> spec = FacetSpec(col="time", row="pressure_level")
    >>> spec.col
    'time'
    >>> spec.row
    'pressure_level'
    
  • Column-wrap layout — 4 panels in a 2x3 grid:

    >>> from pyramids.netcdf.plot_options import FacetSpec
    >>> spec = FacetSpec(col="time", col_wrap=3)
    >>> spec.col_wrap
    3
    
Source code in src/pyramids/netcdf/plot_options.py
@dataclass(frozen=True)
class FacetSpec:
    """Faceting specification for :meth:`NetCDF.plot`.

    When set, ``NetCDF.plot`` builds a stack of slices along the named
    dims and hands them to
    :meth:`cleopatra.array_glyph.ArrayGlyph.facet`. At least one of
    ``col`` or ``row`` must be set; ``row`` alone (without ``col``) is
    invalid and rejected by the validator.

    Attributes:
        col: Band-dim name to facet across columns. Defaults to None.
        row: Band-dim name to facet across rows. Requires ``col``.
            Defaults to None.
        col_wrap: When only ``col`` is set, wrap into this many
            columns (so ``N`` panels lay out as
            ``ceil(N/col_wrap) x col_wrap``). Ignored when ``row`` is
            set. Defaults to None.

    Examples:
        - A column-only facet over the time dim:

            ```python
            >>> from pyramids.netcdf.plot_options import FacetSpec
            >>> spec = FacetSpec(col="time")
            >>> spec.col
            'time'
            >>> spec.row is None
            True

            ```

        - Two-axis facet across time (columns) and pressure level
          (rows):

            ```python
            >>> from pyramids.netcdf.plot_options import FacetSpec
            >>> spec = FacetSpec(col="time", row="pressure_level")
            >>> spec.col
            'time'
            >>> spec.row
            'pressure_level'

            ```

        - Column-wrap layout — 4 panels in a 2x3 grid:

            ```python
            >>> from pyramids.netcdf.plot_options import FacetSpec
            >>> spec = FacetSpec(col="time", col_wrap=3)
            >>> spec.col_wrap
            3

            ```
    """

    col: str | None = None
    row: str | None = None
    col_wrap: int | None = None