uxarray icon indicating copy to clipboard operation
uxarray copied to clipboard

Add `imshow` function for plotting with Matplotlib

Open philipc2 opened this issue 6 months ago • 3 comments

Closes #1268

Overview

There's been strong community demand for first-class Matplotlib support alongside our existing HoloViz workflows. To address this, this PR introduces a native Matplotlib backend based on a screen-space pixel sampling strategy, designed to efficiently visualize unstructured-grid data without expensive geometry constructions.

Key Features

  • Screen-space Sampling:

    • Utilizes our optimized Grid.get_faces_containing_point() method to map each Matplotlib pixel center directly to the corresponding unstructured-grid face.
  • Simplified Matplotlib Interface (uxarray.plot.matplotlib.imshow):

    • Provides a single, intuitive, and self-configuring imshow function that seamlessly integrates with Cartopy for map projections.
    • Employs a nearest-neighbor sampling approach by default, making it straightforward to use and easy to integrate into existing visualization workflows.
  • Performance and Extensibility:

    • By directly sampling in display-space, the backend avoids the computational cost associated with triangulation or and intermediate geometry creation (i.e. with shapely)
    • Establishes a robust foundation for future enhancements, such as adding bilinear or higher-order interpolation methods, without altering the public-facing API (we can add a resample or similar parameter once multiple approaches are supported)

Documentation Changes

I have restructured the "Plotting with Matplotlib" notebook to showcase the imshow functionality instead of the PolyCollection & LineCollection, since these will either be deprecated or reorganized moving forward. They are still available in the API, but will not be immediately covered in the notebook

This addition significantly enhances our visualization toolkit, allowing for efficient and high-quality visualizations with Matplotlib, and lays the groundwork for more advanced interpolation and rendering techniques moving forward.

Usage

import uxarray as ux
import cartopy.crs as ccrs
import uxarray.plot.matplotlib as uxplot

uxda ...
img = uxplot.imshow(uxda) 
image
img = uxplot.imshow(uxda, projection=ccrs.Orthographic()) 
image

End to End Visualization Workflow

fig, ax = plt.subplots(
    subplot_kw={'projection': ccrs.Orthographic(central_longitude=-90, central_latitude=45)},
    constrained_layout=True,
    figsize=(10, 5),
)
scale = '110m'


ax.set_extent([-90, -80, 40, 50])  # great lakes 

var = uxds['lwupt'].isel(Time=0)
my_plot = uxplot.imshow(var, ax=ax)


ax.coastlines(resolution=scale, color='black', linewidth=1)
ax.add_feature(cfeature.BORDERS.with_scale(scale), linestyle=':', linewidth=0.8)
ax.add_feature(cfeature.STATES.with_scale(scale), linestyle=':', linewidth=0.8)
gl = ax.gridlines(
    draw_labels=True,
    linewidth=0.5,
    linestyle='--',
    color='gray',
    alpha=0.7,
)

lakes = NaturalEarthFeature(
    category='physical',
    name='lakes',
    scale='10m',
    edgecolor='black',
    facecolor='none',
    alpha=0.7,
)
ax.add_feature(lakes, linewidth=1, zorder=2)

gl.top_labels = False
gl.right_labels = False

ax.set_title(f"15km MPAS: {var.long_name}", pad=4, fontsize=10)

cbar = fig.colorbar(
    my_plot,
    ax=ax,
    orientation='vertical',
    shrink=0.6,
    pad=0.03,
)
label = rf"$\mathit{{{var.name}}}\ \left[\mathrm{{{var.units}}}\right]$"
cbar.set_label(label, labelpad=8, fontsize=12)

plt.show()
image

philipc2 avatar May 20 '25 16:05 philipc2