Source code for ctd_tools.plotters.ts_diagram_plotter

"""
Module for creating T-S (Temperature-Salinity) diagrams from sensor data.
"""

from __future__ import annotations
import matplotlib.pyplot as plt
import numpy as np
import gsw

from ctd_tools.plotters.base import AbstractPlotter
import ctd_tools.parameters as params


[docs] class TsDiagramPlotter(AbstractPlotter): """Creates T-S (Temperature-Salinity) diagrams from CTD sensor data. This class specializes in creating T-S diagrams, which are scatter plots of temperature vs salinity data points, often colored by depth and with optional density isolines. Attributes: ----------- data : xr.Dataset The xarray Dataset containing the sensor data to be plotted. Methods: -------- plot(output_file=None, title='T-S Diagram', dot_size=70, use_colormap=True, show_density_isolines=True, colormap='jet', show_lines_between_dots=True, show_grid=True): Creates and displays/saves the T-S diagram. _plot_density_isolines(): Adds density isolines to the T-S diagram. """
[docs] def plot(self, output_file: str | None = None, title: str = 'T-S Diagram', dot_size: int = 70, use_colormap: bool = True, show_density_isolines: bool = True, colormap: str = 'jet', show_lines_between_dots: bool = True, show_grid: bool = True, *args, **kwargs): """Creates a T-S diagram plot. Parameters: ----------- output_file : str, optional Path to save the plot. If None, the plot is displayed. title : str, default 'T-S Diagram' Title for the plot. dot_size : int, default 70 Size of the scatter plot markers. use_colormap : bool, default True Whether to color points by depth using a colormap. show_density_isolines : bool, default True Whether to show density isolines on the plot. colormap : str, default 'jet' Matplotlib colormap name to use for depth coloring. show_lines_between_dots : bool, default True Whether to connect data points with lines. show_grid : bool, default True Whether to show grid lines on the plot. Raises: ------- ValueError: If required variables (temperature, salinity, depth) are missing. """ # Validate required variables required_vars = [params.TEMPERATURE, params.SALINITY, params.DEPTH] self._validate_required_variables(required_vars) # Get dataset without NaN values ds = self._get_dataset_without_nan() temperature = ds[params.TEMPERATURE] salinity = ds[params.SALINITY] depth = ds[params.DEPTH] # Check for potential temperature and use it if available if params.POTENTIAL_TEMPERATURE in ds: temperature = ds[params.POTENTIAL_TEMPERATURE] # Create figure fig = plt.figure(figsize=(15, 8)) # Create a line plot of temperature vs. salinity if show_lines_between_dots: plt.plot(salinity, temperature, color='gray', linestyle='-', linewidth=0.5) # Create a scatter plot of temperature vs. salinity if use_colormap: plt.scatter(salinity, temperature, c=depth, cmap=colormap, marker='o', s=dot_size) plt.colorbar(label='Depth [m]') # Plot legend for colormap else: plt.scatter(salinity, temperature, c='black', marker='o', s=dot_size) # Add grid lines to the plot for better readability if show_grid: plt.grid(color='gray', linestyle='--', linewidth=0.5) # Set plot labels and title plt.title(title) plt.xlabel('Salinity [PSU]') # Set y-label based on temperature type if params.POTENTIAL_TEMPERATURE in ds: plt.ylabel(ds[params.POTENTIAL_TEMPERATURE].attrs['long_name'] + \ " [" + ds[params.POTENTIAL_TEMPERATURE].attrs['units'] + "]") else: plt.ylabel(ds[params.TEMPERATURE].attrs['long_name'] + \ " [" + ds[params.TEMPERATURE].attrs['units'] + "]") # Integrate density isolines if wanted if show_density_isolines: self._plot_density_isolines(ds) # Enable tight layout plt.tight_layout() # Save or show the plot self._save_or_show_plot(output_file)
def _plot_density_isolines(self, ds): """Plots density isolines into the T-S diagram. Parameters: ----------- ds : xr.Dataset The dataset containing temperature and salinity data. """ # Define the min / max values for plotting isopycnals t_min = ds[params.TEMPERATURE].values.min() t_max = ds[params.TEMPERATURE].values.max() s_min = ds[params.SALINITY].values.min() s_max = ds[params.SALINITY].values.max() # Calculate "padding" for temperature axis t_width = t_max - t_min t_min -= (t_width * 0.1) t_max += (t_width * 0.1) # Calculate "padding" for salinity axis s_width = s_max - s_min s_min -= (s_width * 0.1) s_max += (s_width * 0.1) # Calculate how many gridcells we need in the x and y dimensions factor = round(max([t_width, s_width]) / min([t_width, s_width])) if s_width > t_width: xdim = 150 ydim = round(150 / factor) else: ydim = 150 xdim = round(150 / factor) density = np.zeros((int(ydim), int(xdim))) # Create temp and salt vectors of appropriate dimensions ti = np.linspace(t_min, t_max, ydim) si = np.linspace(s_min, s_max, xdim) # Loop to fill in grid with densities for j in range(0, int(ydim)): for i in range(0, int(xdim)): density[j, i] = gsw.rho(si[i], ti[j], 0) # Subtract 1000 to convert density to sigma-t sigma_t = density - 1000 # Plot isolines cs = plt.contour(si, ti, sigma_t, linewidths=1, linestyles='dashed', colors='gray') plt.clabel(cs, fontsize=8, inline=1, fmt='%1.2f') # Label every second level # Add sigma_0 in gray in the left upper corner plt.text(0.02, 0.95, r"$\sigma_0$", color='gray', fontsize=18, fontweight='bold', transform=plt.gca().transAxes)