Unstructured Plotting (marEx.plotX.unstructured)

The marEx.plotX.unstructured module provides specialised plotting functionality for unstructured grids, such as those used in finite element ocean models (FESOM, ICON-O, MPAS), atmospheric models on icosahedral grids, and other irregular mesh datasets.

Overview

This module implements the UnstructuredPlotter class, which handles irregular spatial grids where data points are not arranged in a regular rectangular pattern. It provides triangulation and interpolation capabilities for complex mesh structures with global caching for performance optimisation.

Key Features:

  • Triangulation Support: Native triangular mesh visualisation using matplotlib

  • KDTree Interpolation: Fast interpolation to regular grids using pre-computed indices

  • Global Caching: Persistent caching of expensive triangulation and spatial index operations

  • Flexible Input: Supports both triangulation files and KDTree index directories

  • Memory Efficient: Optimised memory usage for large unstructured datasets

Classes and Functions

UnstructuredPlotter(xarray_obj[, ...])

Plotter for unstructured oceanographic data on triangular meshes.

clear_cache()

Clear the global grid cache.

Utility Functions

_load_triangulation(fpath_tgrid)

Load and cache triangulation data globally.

_load_ckdtree(fpath_ckdtree, res)

Load and cache ckdtree data globally.

UnstructuredPlotter Class

class marEx.plotX.unstructured.UnstructuredPlotter(xarray_obj, dimensions=None, coordinates=None)[source]

Bases: PlotterBase

Plotter for unstructured oceanographic data on triangular meshes.

Initialise UnstructuredPlotter.

Parameters:
__init__(xarray_obj, dimensions=None, coordinates=None)[source]

Initialise UnstructuredPlotter.

Parameters:
Return type:

None

specify_grid(fpath_tgrid=None, fpath_ckdtree=None)[source]

Set the path to the unstructured grid files.

Parameters:
Return type:

None

plot(ax, cmap='viridis', clim=None, norm=None)[source]

Implement plotting for unstructured data.

Parameters:
Return type:

Tuple[Axes, TriMesh | QuadMesh]

The UnstructuredPlotter class handles irregular mesh grids:

# UnstructuredPlotter is typically accessed via the plotX accessor
# Automatic selection based on grid type detection

import xarray as xr
import marEx

# Set up grid information first
marEx.specify_grid(
    grid_type='unstructured',
    fpath_tgrid='grid_triangulation.nc',
    fpath_ckdtree='./ckdtree_indices/'
)

# Load unstructured data
data = xr.open_dataset('unstructured_data.nc').temperature

# Plotting automatically uses UnstructuredPlotter
config = marEx.PlotConfig(title='Ocean Model Temperature', var_units='°C')
fig, ax, im = data.plotX.single_plot(config)

Methods

Grid Specification

UnstructuredPlotter.specify_grid(fpath_tgrid=None, fpath_ckdtree=None)[source]

Set the path to the unstructured grid files.

Parameters:
Return type:

None

Set grid file paths for unstructured plotting:

# Method 1: Global specification (recommended)
marEx.specify_grid(
    grid_type='unstructured',
    fpath_tgrid='triangulation.nc',
    fpath_ckdtree='./ckdtree_data/'
)

# Method 2: Per-plotter specification
plotter = UnstructuredPlotter(data)
plotter.specify_grid(
    fpath_tgrid='triangulation.nc',
    fpath_ckdtree='./ckdtree_data/'
)

Plot Method

UnstructuredPlotter.plot(ax, cmap='viridis', clim=None, norm=None)[source]

Implement plotting for unstructured data.

Parameters:
Return type:

Tuple[Axes, TriMesh | QuadMesh]

The core plotting method supports two rendering modes:

  1. KDTree Interpolation (if fpath_ckdtree provided): Fast interpolation to regular grid

  2. Triangulation (if fpath_tgrid provided): Native triangular mesh rendering

Helper Functions

Triangulation Loading

marEx.plotX.unstructured._load_triangulation(fpath_tgrid)[source]

Load and cache triangulation data globally.

Parameters:

fpath_tgrid (str | Path)

Return type:

Triangulation

Loads and caches triangulation data:

# Triangulation files must contain:
# - 'vertex_of_cell': connectivity array (1-based indexing)
# - 'clon': cell longitude coordinates
# - 'clat': cell latitude coordinates

# File format example:
# vertex_of_cell(ncells, nvertices_per_cell) = [[1, 2, 3], [2, 3, 4], ...]
# clon(ncells) = [longitude values]
# clat(ncells) = [latitude values]

KDTree Loading

marEx.plotX.unstructured._load_ckdtree(fpath_ckdtree, res)[source]

Load and cache ckdtree data globally.

Parameters:
Return type:

Dict[str, ndarray[tuple[Any, …], dtype[Any]]]

Loads and caches KDTree interpolation data:

# KDTree directory structure:
# ckdtree_path/
#   ├── res0.10.nc
#   ├── res0.25.nc
#   ├── res0.50.nc
#   └── res1.00.nc

# Each resolution file contains:
# - 'ickdtree_c': indices for interpolation
# - 'lon': regular grid longitude coordinates
# - 'lat': regular grid latitude coordinates

Basic Usage Examples

Setup and Simple Plot

import xarray as xr
import marEx

# Set up grid information globally
marEx.specify_grid(
    grid_type='unstructured',
    fpath_tgrid='ocean_grid.nc',
    fpath_ckdtree='./spatial_indices/'
)

# Load unstructured ocean model data
sst = xr.open_dataset('fesom_sst.nc').sst

# Basic plot
config = marEx.PlotConfig(
    title='Ocean Model SST',
    var_units='°C',
    cmap='thermal',
    show_colorbar=True
)

fig, ax, im = sst.plotX.single_plot(config)

Triangulation-Only Plotting

# Use only triangulation (no interpolation)
marEx.specify_grid(
    grid_type='unstructured',
    fpath_tgrid='triangulation.nc'
    # No ckdtree path - will use native triangulation
)

config = marEx.PlotConfig(
    title='Native Mesh Visualization',
    var_units='Temperature (°C)',
    cmap='plasma',
    show_colorbar=True
)

fig, ax, im = data.plotX.single_plot(config)

KDTree-Only Plotting

# Use only KDTree interpolation
marEx.specify_grid(
    grid_type='unstructured',
    fpath_ckdtree='./interpolation_indices/'
    # No triangulation path - will use interpolated regular grid
)

config = marEx.PlotConfig(
    title='Interpolated Visualization',
    var_units='°C',
    cmap='coolwarm',
    show_colorbar=True
)

fig, ax, im = data.plotX.single_plot(config)

Time Series Visualization

Multi-Panel Plots

# Plot multiple time steps
config = marEx.PlotConfig(
    var_units='°C',
    cmap='RdBu_r',
    issym=True,
    show_colorbar=True
)

# Create wrapped subplots
fig, axes = sst.plotX.multi_plot(config, col='time', col_wrap=3)

Animation

# Create animation
config = marEx.PlotConfig(
    title='Ocean Model Evolution',
    var_units='°C',
    cmap='thermal',
    show_colorbar=True
)

# Generate animation (requires ffmpeg)
movie_path = sst.plotX.animate(
    config,
    plot_dir='./animations',
    file_name='ocean_evolution'
)

Advanced Configuration

Custom Dimension Names

# For unstructured data with custom dimension names
config = marEx.PlotConfig(
    title='Custom Dimensions',
    var_units='°C',
    cmap='viridis',
    # Custom dimension mapping for unstructured data
    dimensions={'time': 'time', 'x': 'cell_index'},
    coordinates={'time': 'time', 'x': 'cell_lon', 'y': 'cell_lat'}
)

fig, ax, im = data.plotX.single_plot(config)

Grid Type Detection

The UnstructuredPlotter is automatically selected when:

  • Data has a single spatial dimension (e.g., ncells)

  • Latitude and longitude are coordinates rather than dimensions

  • Typical structure: (time, ncells) with lat(ncells) and lon(ncells) coordinates

# Automatic detection based on:
# - Single spatial dimension in data.dims
# - lat/lon as coordinates (not dimensions)
# - Irregular spatial arrangement

# Example unstructured data structure:
# Dimensions: (time: 365, ncells: 830305)
# Coordinates: lat(ncells), lon(ncells), time(time)

# Override detection if needed:
marEx.specify_grid(grid_type='unstructured')

File Format Requirements

Triangulation Files

Triangulation files must contain specific variables:

# Required variables in triangulation NetCDF file:
# - vertex_of_cell(ncells, nvertices_per_cell): connectivity array
# - clon(ncells): cell longitude coordinates
# - clat(ncells): cell latitude coordinates

# Example file creation:
import xarray as xr
import numpy as np

# Create triangulation file
triangulation_ds = xr.Dataset({
    'vertex_of_cell': (['ncells', 'nvertices'], connectivity_array),
    'clon': (['ncells'], lon_coords),
    'clat': (['ncells'], lat_coords)
})
triangulation_ds.to_netcdf('triangulation.nc')

KDTree Index Files

KDTree directories contain resolution-specific files:

# Directory structure:
# ckdtree_indices/
#   ├── res0.10.nc  # High resolution
#   ├── res0.25.nc  # Medium resolution
#   ├── res0.50.nc  # Low resolution
#   └── res1.00.nc  # Very low resolution

# Each file contains:
# - ickdtree_c(nlat, nlon): indices for interpolation
# - lon(nlon): regular grid longitude coordinates
# - lat(nlat): regular grid latitude coordinates

# Example file creation:
ckdtree_ds = xr.Dataset({
    'ickdtree_c': (['nlat', 'nlon'], index_array),
    'lon': (['nlon'], regular_lon),
    'lat': (['nlat'], regular_lat)
})
ckdtree_ds.to_netcdf('res0.25.nc')

Integration Examples

Matplotlib Integration

import matplotlib.pyplot as plt

# Create custom figure
fig, ax = plt.subplots(figsize=(12, 8))

# Use existing axes
config = marEx.PlotConfig(title='Custom Layout', var_units='°C')
fig, ax, im = data.plotX.single_plot(config, ax=ax)

# Add custom annotations
ax.set_title('Ocean Model Results', fontsize=16)
plt.tight_layout()

Comparison Plots

# Compare model vs observations
fig, axes = plt.subplots(1, 2, figsize=(16, 6))

# Model data (unstructured)
marEx.specify_grid(grid_type='unstructured', fpath_ckdtree='./model_indices/')
config1 = marEx.PlotConfig(title='Model', var_units='°C', show_colorbar=False)
fig, axes[0], im1 = model_data.plotX.single_plot(config1, ax=axes[0])

# Observations (structured)
marEx.specify_grid(grid_type='gridded')
config2 = marEx.PlotConfig(title='Observations', var_units='°C', show_colorbar=False)
fig, axes[1], im2 = obs_data.plotX.single_plot(config2, ax=axes[1])

# Single colorbar
fig.subplots_adjust(right=0.9)
cbar_ax = fig.add_axes([0.92, 0.15, 0.02, 0.7])
fig.colorbar(im1, cax=cbar_ax, extend='both')

See Also