Vector plotting methods

Vector plotting methods#

To plot vector quantities such as currents we can use a quiver plot in matplotlib. In the GBR4 tutorial dataset the u and v variables contain the x and y components of the vector for each cell in the dataset.

import matplotlib.pyplot as plt
import numpy
import pandas
import xarray
from shapely import box

import emsarray
from emsarray import plot
from emsarray.operations import point_extraction

dataset = emsarray.tutorial.open_dataset('gbr4')

surface_currents = dataset.ems.select_variables(["temp", "u", "v"]).isel(time=0, k=-1)
surface_currents.load()
<xarray.Dataset> Size: 4MB
Dimensions:    (j: 180, i: 600)
Coordinates:
    latitude   (j, i) float64 864kB -8.074 -8.107 -8.139 ... -28.56 -28.59
    longitude  (j, i) float64 864kB 142.2 142.2 142.2 ... 156.9 156.9 156.9
    time       datetime64[ns] 8B 2022-10-15T14:00:00
    zc         float64 8B 1.5
Dimensions without coordinates: j, i
Data variables:
    temp       (j, i) float64 864kB nan nan nan nan ... 20.9 20.92 20.93 20.93
    u          (j, i) float64 864kB nan nan nan ... -0.005159 0.03509 0.05466
    v          (j, i) float64 864kB nan nan nan nan ... 0.08804 0.08797 0.04464
Attributes: (12/16)
    title:                           gbr4_H4p0_ABARRAr2_OBRAN2020_FG2Gv3_Dhnd
    description:                     eReefs GBR 4k grid with 64 active river ...
    paramhead:                       eReefs GBR 4k grid, with Atmospheric for...
    paramfile:                       /g/data/et4/projects/eReefs/gbr4_H4p0_AB...
    ems_version:                     v1.4.0 rev(7365)
    date_created:                    Mon Oct 21 15:47:23 2024
    ...                              ...
    bald__isPrefixedBy:              prefix_list
    metadata_link:                   https://marlin.csiro.au/geonetwork/srv/e...
    prefix_list_puv__:               https://w3id.org/env/puv#
    DODS_EXTRA.Unlimited_Dimension:  time
    history:                         Wed Jan 14 14:15:20 2026: ncks -d time,0...
    NCO:                             netCDF Operators version 5.3.6 (Homepage...


Plotting every cell is straight forward for small areas:

figure = plt.figure(figsize=(7, 8), layout="constrained")
axes = figure.add_subplot(projection=dataset.ems.data_crs)
figure.suptitle("Surface water temperature and currents near the Whitsundays")

temp = surface_currents["temp"]
temp_artist = dataset.ems.make_artist(
    axes, temp,
    cmap="coolwarm", clim=(24, 27))

current_artist = dataset.ems.make_artist(
    axes, (surface_currents["u"], surface_currents["v"]),
    scale=20, scale_units="width")

plot.add_coast(axes)
plot.add_gridlines(axes)

# Just show a small area over the Whitsundays
view_box = box(148.7, -20.4, 149.6, -19.8)
axes.set_aspect("equal", adjustable="datalim")
axes.set_extent(plot.bounds_to_extent(view_box.bounds))
Surface water temperature and currents near the Whitsundays

Plotting the entire dataset this way leads to the current vectors becoming a confusing mess however:

figure = plt.figure(figsize=(7, 10), layout="constrained")
axes = figure.add_subplot(projection=dataset.ems.data_crs)
figure.suptitle("A bad plot of surface water temperature and currents over the entire reef")

temp = surface_currents["temp"]
temp_artist = dataset.ems.make_artist(axes, temp, cmap="coolwarm")

current_artist = dataset.ems.make_artist(
    axes, (surface_currents["u"], surface_currents["v"]),
    scale=20, scale_units="width")

plot.add_coast(axes)
plot.add_gridlines(axes)

# Show the entire model domain
axes.set_aspect("equal", adjustable="datalim")
axes.autoscale()
A bad plot of surface water temperature and currents over the entire reef

Sampling gridded datasets#

For datasets on a two dimensional grid like this we can sample the current data at regular intervals to display only a subset of the vectors. The sampled points will follow the geometry of the dataset. For curvilinear datasets such as GBR4 the vectors will follow the curves of the dataset shape:

# Make an empty array of the same shape as the data, then select every 10th cell in there.
face_grid = dataset.ems.get_grid(surface_currents["u"])
samples = xarray.DataArray(numpy.full(face_grid.size, False))
samples = face_grid.wind(samples)
samples[::10, ::10] = True
samples = dataset.ems.ravel(samples)

# Select the (x, y) coordinates and the (u, v) components of the sampled cells
x, y = face_grid.centroid_coordinates[samples].T
u = dataset.ems.ravel(surface_currents["u"]).values[samples]
v = dataset.ems.ravel(surface_currents["v"]).values[samples]

# Plot the points
figure = plt.figure(figsize=(7, 10), layout="constrained")
axes = figure.add_subplot(projection=dataset.ems.data_crs)
figure.suptitle("Surface water temperature and currents across the entire dataset domain")

temp = surface_currents["temp"]
temp_artist = dataset.ems.make_artist(axes, temp, cmap="coolwarm")

quiver = plt.quiver(
    x, y, u, v,
    scale=40, scale_units="width")
axes.add_collection(quiver)

plot.add_coast(axes)
plot.add_gridlines(axes)

# Show the entire model domain
axes.set_aspect("equal", adjustable="datalim")
axes.autoscale()
Surface water temperature and currents across the entire dataset domain

Sampling the dataset domain#

Another approach is to plot vectors at regular points across the dataset domain by sampling at regular intervals. The vector locations are not tied to the grid geometry. This approach will work with unstructured grids unlike the previous method. point_extraction.extract_dataframe() can be used for this:

# Generate a mesh of points across the dataset domain
domain = box(*dataset.ems.bounds)
x = numpy.arange(domain.bounds[0], domain.bounds[2], 0.4)
y = numpy.arange(domain.bounds[1], domain.bounds[3], 0.4)
xx, yy = numpy.meshgrid(x, y)
points = pandas.DataFrame({
    'x': xx.flatten(),
    'y': yy.flatten(),
})

# Extract the surface current components at these locations
components = point_extraction.extract_dataframe(
    surface_currents, points, ('x', 'y'), missing_points='drop')

# Plot the points
figure = plt.figure(figsize=(7, 10), layout="constrained")
axes = figure.add_subplot(projection=dataset.ems.data_crs)
figure.suptitle("Surface water temperature and currents across the entire dataset domain")

temp = surface_currents["temp"]
temp_artist = dataset.ems.make_artist(axes, temp, cmap="coolwarm")

quiver = plt.quiver(
    components['x'], components['y'], components['u'], components['v'],
    scale=30, scale_units="width")
axes.add_collection(quiver)

plot.add_coast(axes)
plot.add_gridlines(axes)

axes.set_aspect("equal", adjustable="datalim")
axes.autoscale()
Surface water temperature and currents across the entire dataset domain

Total running time of the script: (0 minutes 14.574 seconds)

Gallery generated by Sphinx-Gallery