import numpy as np import xarray as xr import gsw import plotly.graph_objects as go import pandas as pd def get_valid_data(ds_prof): def get_var(name): adj_name = f"{name}_ADJUSTED" if adj_name in ds_prof: val = ds_prof[adj_name].values.flatten() if not np.isnan(val).all(): return val if name in ds_prof: return ds_prof[name].values.flatten() # Fallback if variable doesn't exist if 'PRES' in ds_prof: return np.full_like(ds_prof.PRES.values.flatten(), np.nan) return np.array([]) if 'CYCLE_NUMBER' not in ds_prof or 'PRES' not in ds_prof: return np.array([]), np.array([]), np.array([]), np.array([]), np.array([]), np.array([]) cycles_2d = np.repeat(ds_prof.CYCLE_NUMBER.values[:, np.newaxis], ds_prof.PRES.shape[1], axis=1) dates_2d = np.repeat(ds_prof.JULD.values[:, np.newaxis], ds_prof.PRES.shape[1], axis=1) lon = ds_prof.LONGITUDE.values lat = ds_prof.LATITUDE.values lon_2d = np.repeat(lon[:, np.newaxis], ds_prof.PRES.shape[1], axis=1) lat_2d = np.repeat(lat[:, np.newaxis], ds_prof.PRES.shape[1], axis=1) pres = get_var('PRES') temp = get_var('TEMP') psal = get_var('PSAL') cycles = cycles_2d.flatten() dates = dates_2d.flatten() lon_flat = lon_2d.flatten() lat_flat = lat_2d.flatten() valid = ~np.isnan(pres) & ~np.isnan(temp) & ~np.isnan(psal) & ~np.isnat(dates) pres = pres[valid] temp = temp[valid] psal = psal[valid] cycles = cycles[valid] dates = dates[valid] lon_flat = lon_flat[valid] lat_flat = lat_flat[valid] SA = gsw.SA_from_SP(psal, pres, lon_flat, lat_flat) CT = gsw.CT_from_t(SA, temp, pres) rho = gsw.sigma0(SA, CT) return cycles, dates, pres, temp, psal, rho def _dark_layout(title, xlabel, ylabel, invert_y=False): layout = dict( title=title, xaxis_title=xlabel, yaxis_title=ylabel, paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", font=dict(family="Inter, sans-serif", color="#c8d6e5", size=12), margin=dict(l=40, r=20, t=40, b=40), xaxis=dict(gridcolor="rgba(255,255,255,0.1)", zerolinecolor="rgba(255,255,255,0.1)"), yaxis=dict(gridcolor="rgba(255,255,255,0.1)", zerolinecolor="rgba(255,255,255,0.1)") ) if xlabel == "Date": layout["xaxis"]["tickformat"] = "%d/%m/%Y" if invert_y: layout["yaxis"]["autorange"] = "reversed" return layout def create_ts_diagram(cycles, temp, psal, wmo, title="T/S Diagram"): fig = go.Figure() fig.add_trace(go.Scattergl( x=psal, y=temp, mode='markers', marker=dict( size=4, color=cycles, colorscale='Jet', showscale=True, colorbar=dict(title="Profile
number") ), customdata=np.stack((cycles,), axis=-1), hovertemplate="Cycle: %{customdata[0]}
Sal: %{x:.3f} PSU
Temp: %{y:.3f}°C" )) fig.update_layout(**_dark_layout(title, "Practical Salinity (PSU)", "Temperature (°C)")) return fig def create_section_chart(dates, pres, z_var, z_label, title, wmo, cmap='Jet'): fig = go.Figure() fig.add_trace(go.Scattergl( x=dates, y=pres, mode='markers', marker=dict( size=5, symbol='square', color=z_var, colorscale=cmap, showscale=True, colorbar=dict(title=z_label.replace(' ', '
', 1)) ), customdata=np.stack((z_var,), axis=-1), hovertemplate="Date: %{x|%Y-%m-%d %H:%M}
Press: %{y:.1f} dbar
" + z_label + ": %{customdata[0]:.3f}" )) fig.update_layout(**_dark_layout(title, "Date", "Pressure (dbar)", invert_y=True)) return fig def create_overlaid_profiles(x_var, pres, cycles, x_label, title, wmo, cmap='Jet'): fig = go.Figure() fig.add_trace(go.Scattergl( x=x_var, y=pres, mode='markers', marker=dict( size=3, color=cycles, colorscale=cmap, showscale=True, colorbar=dict(title="Profile
number") ), customdata=np.stack((cycles,), axis=-1), hovertemplate="Cycle: %{customdata[0]}
" + x_label + ": %{x:.3f}
Press: %{y:.1f} dbar" )) fig.update_layout(**_dark_layout(title, x_label, "Pressure (dbar)", invert_y=True)) return fig