165 lines
5.6 KiB
Matlab
165 lines
5.6 KiB
Matlab
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
|