function ax = plotCryoData(tbl, tStart, tEnd) % plotCryoData Plot cryo cell data in 3 stacked panels with shared datetime axis. % % [ax] = plotCryoData(tbl, tStart, tEnd) % Returns struct with handles to the 3 axes for easy limit modification. % % The first input may be EITHER: % * a table with the columns datetime, pressure_cell_hPa_, % temperature__C_, x_mm_, y_mm_, z_mm_, flowCryo_l_min_, OR % * a LabView structure produced by LabView_importData (optionally % combined with LabView_combine). In that case it is converted to the % required table on the fly (see the local function labviewToTable). %--- Accept a LabView timeseries structure and convert it to a table --- if isstruct(tbl) tbl = labviewToTable(tbl); end %--- Extract datetime column --- dt = tbl.datetime; %--- If start/stop provided, subset the table --- if nargin >= 3 && ~isempty(tStart) && ~isempty(tEnd) mask = dt >= tStart & dt <= tEnd; tbl = tbl(mask, :); dt = dt(mask); end %--- Create tiled layout --- figure; tl = tiledlayout(4, 1); tl.TileSpacing = 'compact'; tl.Padding = 'compact'; %--- Upper tile: pressure and temperature (spans 2 rows) --- ax.pressure_temp = nexttile([2 1]); yyaxis left; plot(dt, tbl.pressure_cell_hPa_, 'o', 'MarkerSize', 2, 'MarkerFaceColor', 'auto'); ylabel('Pressure [hPa]'); ylim padded; grid on; yyaxis right; plot(dt, tbl.temperature__C_, 'o', 'MarkerSize', 2, 'MarkerFaceColor', 'auto'); ylabel('Temperature [°C]'); ylim padded; title('Pressure and Temperature'); set(gca, 'XTickLabel', []); %--- Middle tile: x, y, z with dual y-axes --- ax.xyz = nexttile; yyaxis left; plot(dt, tbl.y_mm_, 'o', 'MarkerSize', 2, 'MarkerFaceColor', 'auto', 'DisplayName', 'y'); hold on; ylabel('Y [mm]'); ylim([10 15]); grid on; yyaxis right; plot(dt, tbl.x_mm_, 's', 'MarkerSize', 2, 'MarkerFaceColor', 'auto', 'DisplayName', 'x'); hold on; plot(dt, tbl.z_mm_, '^', 'MarkerSize', 2, 'MarkerFaceColor', 'auto', 'DisplayName', 'z'); ylabel('X, Z [mm]'); ylim([-2 2]); legend({'y', 'x', 'z'}, 'Location', 'best'); set(gca, 'XTickLabel', []); %--- Lower tile: flowCryo --- ax.flow = nexttile; plot(dt, tbl.flowCryo_l_min_, 'o', 'MarkerSize', 2, 'MarkerFaceColor', 'auto'); grid on; ylabel('Flow [L/min]'); xlabel('Date & Time'); title('Cryo Flow'); %--- Link x-axes for all tiles --- linkaxes([ax.pressure_temp, ax.xyz, ax.flow], 'x'); % Improve datetime formatting on x-axis ax.flow.XAxis.TickLabelFormat = 'dd-MMM HH:mm'; end % ========================================================================= % LOCAL FUNCTIONS % ========================================================================= function tbl = labviewToTable(LabView, tsName) %LABVIEWTOTABLE Convert a LabView cryo-log timeseries into a plotCryoData table. % % Uses LabView.merge if present (created by LabView_combine); otherwise the % timeseries field named tsName, or the first timeseries in the structure. % The mapping uses the FIXED ts.Data column order that LabView_importData % produces for every file format: % [IceTemp, ResP1, ResP2, cell/Baratron1, Cooler1, Cooler2, FlowHe, ... % Baratron2, Baratron3, x, y, z, Tsealing] % so it works for old and new files alike. Channels a given file does not % contain (e.g. x/y/z in pre-2019 logs) are returned as NaN. %--- Select which timeseries to convert --- if nargin >= 2 && ~isempty(tsName) if ~isfield(LabView, tsName) error('plotCryoData:noSuchField', ... 'LabView has no timeseries field "%s".', tsName); end ts = LabView.(tsName); elseif isfield(LabView, 'merge') ts = LabView.merge; % combined log (preferred) else fn = fieldnames(LabView); if isempty(fn) error('plotCryoData:emptyStruct', ... 'LabView structure contains no timeseries.'); end ts = LabView.(fn{1}); % single-file case end %--- Absolute time as a datetime column --- try dt = datetime(getabstime(ts)); catch dt = datetime(ts.TimeInfo.StartDate) + ... seconds(ts.Time * unitToSeconds(ts.TimeInfo.Units)); end dt = dt(:); %--- Pull channels by their fixed ts.Data column index --- D = ts.Data; tbl = table( ... dt, ... getCol(D, 4), ... % pressure_cell (hPa) <- cell / Baratron-1 slot getCol(D, 1), ... % temperature (degC) <- ice sample getCol(D, 10), ... % x (mm) getCol(D, 11), ... % y (mm) getCol(D, 12), ... % z (mm) getCol(D, 7), ... % flow cryo (l/min) <- He cryoholder 'VariableNames', {'datetime', 'pressure_cell_hPa_', 'temperature__C_', ... 'x_mm_', 'y_mm_', 'z_mm_', 'flowCryo_l_min_'}); end function c = getCol(D, k) % Return column k of D, or a NaN column if that channel is absent % (e.g. pre-2019 logs have only 9 columns, so no x/y/z). if size(D, 2) >= k c = D(:, k); else c = nan(size(D, 1), 1); end end function s = unitToSeconds(u) % Convert timeseries TimeInfo.Units to seconds (fallback path only). switch lower(u) case 'seconds', s = 1; case 'minutes', s = 60; case 'hours', s = 3600; case 'days', s = 86400; case 'milliseconds', s = 1e-3; otherwise, s = 1; % assume seconds end end