Skip to content

Commit 7c1d791

Browse files
committed
Added time series plots
1 parent d3f7197 commit 7c1d791

File tree

3 files changed

+523
-0
lines changed

3 files changed

+523
-0
lines changed

auxil/plot_timeseries.m

Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
function plot_timeseries(Data, Mdata, variables, depth, basename, varargin)
2+
% plot_timeseries This function is part of the
3+
% MATLAB toolbox for accessing BGC Argo float data.
4+
%
5+
% USAGE:
6+
% plot_timeseries(Data, Mdata, variables, depth, basename, varargin)
7+
%
8+
% DESCRIPTION:
9+
% This function plots time series of one or more specified float(s) for
10+
% the specified variable(s).
11+
%
12+
% PREREQUISITE:
13+
% Sprof file(s) for the specified float(s) must exist locally.
14+
%
15+
% INPUTS:
16+
% Data : struct that must contain the PRES field and the given
17+
% variables (_ADJUSTED fields are used if available)
18+
% Mdata : struct that must contain the WMO_ID field
19+
% variables : cell array with names of the measured fields (e.g., DOXY)
20+
% depth : array of depth levels to plot
21+
% basename : if not empty, create png files of all plots;
22+
% if per_float is used, the file names will be
23+
% <basename>_<WMOID>_<variable>.png,
24+
% if per_float is not used, the file names will be
25+
% <basename>_<variable>.png
26+
%
27+
% OPTIONAL INPUTS:
28+
% 'end',end_date : end date (in one of the following formats:
29+
% [YYYY MM DD HH MM SS] or [YYYY MM DD])
30+
% 'legend',legend : legend (string) can be 'yes' to show legend along with
31+
% plot (default) or 'no'
32+
% 'per_float',per_float : show time series separately for each float (1)
33+
% or all in one plot (0); default: 1
34+
% 'qc',flags : show only values with the given QC flags (array)
35+
% 0: no QC was performed;
36+
% 1: good data;
37+
% 2: probably good data;
38+
% 3: probably bad data that are potentially correctable;
39+
% 4: bad data;
40+
% 5: value changed;
41+
% 6,7: not used;
42+
% 8: estimated value;
43+
% 9: missing value
44+
% default setting: 0:9 (all flags)
45+
% See Table 7 in Bittig et al.:
46+
% https://www.frontiersin.org/files/Articles/460352/fmars-06-00502-HTML-r1/image_m/fmars-06-00502-t007.jpg
47+
% 'raw',raw : plot raw, i.e., unadjusted data if set to 'yes';
48+
% default: 'no' (i.e., plot adjusted data if
49+
% available)
50+
% 'start',start_date : start date (in one of the following formats:
51+
% [YYYY MM DD HH MM SS] or [YYYY MM DD])
52+
% 'time_label',label : use either years or months ('m'); default depends
53+
% on length of time shown ('m' for up to 18 months,
54+
% 'title',title : title for the plot (default: "Depth: .. db"); an
55+
% empty string ('') suppresses the title
56+
% 'var2',variable : if variable is not empty, profiles of this second
57+
% variable will be plotted; if it is the same type as the
58+
% first variable (e.g., DOXY2 compared to DOXY), it will
59+
% be plotted using the same axes; otherwise, the right
60+
% axis will be used for the second variable
61+
%
62+
% OUTPUT: None
63+
%
64+
% AUTHORS:
65+
% H. Frenzel, J. Sharp, A. Fassbender (NOAA-PMEL), N. Buzby (UW),
66+
% J. Plant, T. Maurer, Y. Takeshita (MBARI), D. Nicholson (WHOI),
67+
% and A. Gray (UW)
68+
%
69+
% CITATION:
70+
% H. Frenzel*, J. Sharp*, A. Fassbender, N. Buzby, J. Plant, T. Maurer,
71+
% Y. Takeshita, D. Nicholson, A. Gray, 2021. BGC-Argo-Mat: A MATLAB
72+
% toolbox for accessing and visualizing Biogeochemical Argo data.
73+
% Zenodo. https://doi.org/10.5281/zenodo.4971318.
74+
% (*These authors contributed equally to the code.)
75+
%
76+
% LICENSE: bgc_argo_mat_license.m
77+
%
78+
% DATE: DECEMBER 1, 2021 (Version 1.1)
79+
80+
global Settings;
81+
82+
if nargin < 5
83+
warning(['Usage: plot_timeseries(Data, Mdata, variables, depth, ', ...
84+
'basename [, varargin])'])
85+
return;
86+
end
87+
88+
% set defaults
89+
per_float = 1; % show time series for each float in a separate plot
90+
raw = 'no'; % plot adjusted data by default
91+
title1 = []; % generate default titles
92+
qc_flags = 0:9; % use all data
93+
var2_orig = [];
94+
lgnd = 'yes';
95+
start_date = [];
96+
end_date = [];
97+
time_label = []; % used as flag
98+
99+
% parse optional arguments
100+
for i = 1:2:length(varargin)-1
101+
if strcmpi(varargin{i}, 'per_float')
102+
per_float = varargin{i+1};
103+
elseif strcmpi(varargin{i}, 'raw')
104+
raw = varargin{i+1};
105+
elseif strcmpi(varargin{i}, 'title')
106+
title1 = varargin{i+1};
107+
elseif strcmpi(varargin{i}, 'qc')
108+
qc_flags = varargin{i+1};
109+
elseif strcmpi(varargin{i}, 'var2')
110+
var2_orig = varargin{i+1};
111+
elseif strcmpi(varargin{i}, 'legend')
112+
lgnd = varargin{i+1};
113+
elseif strcmpi(varargin{i}, 'time_label')
114+
time_label = varargin{i+1};
115+
elseif strcmp(varargin{i}, 'start')
116+
if length(varargin{i+1}) == 3 || length(varargin{i+1}) == 6
117+
start_date = datenum(varargin{i+1});
118+
else
119+
warning(['dates should be in [YYYY MM DD HH MM SS] or ', ...
120+
'[YYYY MM DD] format']);
121+
end
122+
elseif strcmp(varargin{i}, 'end')
123+
if length(varargin{i+1}) == 3 || length(varargin{i+1}) == 6
124+
end_date = datenum(varargin{i+1});
125+
else
126+
warning(['dates should be in [YYYY MM DD HH MM SS] or ', ...
127+
'[YYYY MM DD] format']);
128+
end
129+
else
130+
warning('unknown option: %s', varargin{i});
131+
end
132+
end
133+
134+
if ~isempty(var2_orig) && ~per_float
135+
warning('var2 can only be used with per float time series plots')
136+
return
137+
end
138+
139+
floats = fieldnames(Data);
140+
nfloats = length(floats);
141+
if ~nfloats
142+
warning('no floats found in Data structure')
143+
return
144+
end
145+
146+
float_ids = fieldnames(Mdata);
147+
nvars = length(variables);
148+
ndepths = length(depth);
149+
if per_float
150+
nplots = nfloats * nvars * ndepths;
151+
warn_insert = 'floats, ';
152+
else
153+
nplots = nvars * ndepths;
154+
warn_insert = '';
155+
end
156+
if nplots > Settings.max_plots
157+
warning(['too many plots requested (%d) - use fewer %sdepths and/or variables', ...
158+
newline, 'or increase Settings.max_plots (%d) if possible'], ...
159+
nplots, warn_insert, Settings.max_plots)
160+
return
161+
end
162+
163+
if ~isempty(var2_orig)
164+
[var2{1:nvars}] = deal(var2_orig); % default; may change later
165+
end
166+
[title_added{1:nvars}] = deal(''); % default; may change later
167+
% unless 'raw' is specified, plot adjusted data
168+
if strncmpi(raw,'y',1)
169+
if ~ischar(title1) || ~isempty(title1) % not added if title1 == ''
170+
[title_added{1:nvars}] = deal(' [raw values]');
171+
end
172+
else
173+
for v = 1:nvars
174+
% if all floats have adjusted values available for a variable,
175+
% they will be used instead of raw values
176+
has_adj = 0;
177+
for f = 1:nfloats
178+
% the "_ADJUSTED" variable usually exists, but it may not
179+
% be filled with actual values
180+
if isfield(Data.(floats{f}),[variables{v}, '_ADJUSTED']) && ...
181+
sum(isfinite(Data.(floats{f}).([variables{v}, '_ADJUSTED'])(:)))
182+
has_adj = has_adj + 1;
183+
else
184+
warning(['adjusted values for %s for float %s are not available,',...
185+
' showing raw values for all floats instead'], ...
186+
variables{v}, floats{f});
187+
if ~ischar(title1) || ~isempty(title1)
188+
title_added{v} = [title_added{v}, ' [raw values]'];
189+
end
190+
break
191+
end
192+
if ~isempty(var2_orig)
193+
if isfield(Data.(floats{f}),[var2_orig, '_ADJUSTED']) && ...
194+
sum(isfinite(Data.(floats{f}).([var2_orig, '_ADJUSTED'])(:)))
195+
has_adj = has_adj + 1;
196+
else
197+
warning(['adjusted values for %s for float %s ', ...
198+
'are not available, showing raw values for ', ...
199+
'all floats instead'], var2_orig, floats{f});
200+
if ~ischar(title1) || ~isempty(title1)
201+
title_added{v} = ' [raw values]';
202+
end
203+
break
204+
end
205+
end
206+
end
207+
if has_adj == nfloats * (1 + ~isempty(var2_orig))
208+
variables{v} = [variables{v}, '_ADJUSTED'];
209+
if ~isempty(var2_orig)
210+
var2{v} = [var2_orig, '_ADJUSTED'];
211+
end
212+
end
213+
end
214+
end
215+
216+
if ~isempty(basename)
217+
[var2_insert{1:nvars}] = deal('');
218+
if ~isempty(var2_orig)
219+
for v = 1:nvars
220+
var2_insert{v} = sprintf('_%s', var2{v});
221+
end
222+
end
223+
end
224+
225+
% vertical interpolation to depths with regular intervals
226+
for f = 1:nfloats
227+
Datai.(floats{f}) = depth_interp(Data.(floats{f}), qc_flags, 'raw', raw);
228+
end
229+
230+
% determine indices matching requested depths
231+
ndepths = length(depth);
232+
idx = cell(nfloats, ndepths);
233+
press = cell(nfloats, ndepths); % actual pressure levels used
234+
for d = 1:ndepths
235+
for f = 1:nfloats
236+
[mini, idx{f,d}] = min(abs(Datai.(floats{f}).PRES(:,1) - depth(d)));
237+
press{f,d} = Datai.(floats{f}).PRES(idx{f,d},1);
238+
if mini > Settings.depth_tol
239+
warning(['Closest depth level to requested %.1f db for ', ...
240+
'float %s is %.1f db'], depth(d), floats{f}, press{f,d});
241+
end
242+
end
243+
end
244+
245+
for v = 1:nvars
246+
if ~isempty(var2_orig)
247+
same_var_type = strncmp(variables{v}, var2{v}, ...
248+
length(variables{v}) - ...
249+
9 * endsWith(variables{v}, '_ADJUSTED'));
250+
end
251+
for d = 1:ndepths
252+
if ~per_float
253+
% one figure per variable for all floats; make wide plot
254+
f1 = figure('Position',[20 20 800 400]);
255+
% reset color order
256+
set(gca,'ColorOrderIndex',1);
257+
hold(gca, 'on')
258+
end
259+
for f = 1:nfloats
260+
if per_float
261+
% one figure per variable for each float; make wide plot
262+
f1 = figure('Position',[20 20 800 400]);
263+
set(gca,'ColorOrderIndex',1);
264+
hold(gca, 'on')
265+
end
266+
plot(Datai.(floats{f}).TIME(1,:), ...
267+
Datai.(floats{f}).(variables{v})(idx{f,d},:), 'LineWidth', 2);
268+
xl = xlim;
269+
if ~isempty(start_date) || ~isempty(end_date)
270+
if ~isempty(start_date)
271+
xl(1) = start_date;
272+
end
273+
if ~isempty(end_date)
274+
xl(2) = end_date;
275+
end
276+
xlim(xl);
277+
end
278+
% determine type of time label based on length of time series
279+
if isempty(time_label)
280+
if xl(2) - xl(1) > 548 % in days; ~1.5 years
281+
time_label = 'y';
282+
else
283+
time_label = 'm';
284+
end
285+
end
286+
if strncmpi(time_label, 'y', 1)
287+
set(gca,'XTick',datenum([(2000:2030)' ones(31,1) ones(31,1)]));
288+
datetick('x','yyyy','keeplimits','keepticks');
289+
xlabel('Year','FontSize',14);
290+
else
291+
set(gca,'XTick',datenum([repelem((2000:2030)',12,1) ...
292+
repmat((1:12)',31, 1) ones(31*12,1)]));
293+
datetick('x','mm-yyyy','keeplimits','keepticks');
294+
xlabel('Month','FontSize',14);
295+
end
296+
297+
[long_name, units] = get_var_name_units(variables{v});
298+
ylabel([long_name, ' ', units])
299+
if ~isempty(var2_orig)
300+
if ~same_var_type
301+
yyaxis right;
302+
end
303+
plot(Datai.(floats{f}).TIME(1,:), ...
304+
Datai.(floats{f}).(var2{v})(idx{f,d},:), 'LineWidth', 2);
305+
if ~same_var_type
306+
[long_name, units] = get_var_name_units(var2_orig);
307+
ylabel([long_name, ' ', units])
308+
end
309+
end
310+
if per_float
311+
hold off
312+
box on;
313+
if isempty(title1) && ~ischar(title1) % i.e., []
314+
if strcmp(lgnd, 'no') || ~isempty(var2_orig)
315+
% add float number to title instead
316+
title(sprintf('Depth: %d db (%s)%s', press{f,d}, ...
317+
floats{f}, title_added{v}));
318+
else % legend shows float number
319+
title(sprintf('Depth: %d db%s', press{f,d}, ...
320+
title_added{v}));
321+
end
322+
else
323+
title([title1, title_added{v}]);
324+
end
325+
if strcmp(lgnd,'yes')
326+
if isempty(var2_orig)
327+
legend(float_ids{f},'location','eastoutside',...
328+
'AutoUpdate','off');
329+
else
330+
legend({strrep(variables{v},'_','\_'); ...
331+
strrep(var2{v},'_','\_')}, ...
332+
'location', 'eastoutside', 'AutoUpdate', 'off');
333+
end
334+
end
335+
if ~isempty(basename)
336+
fn_png = sprintf('%s_%d_%s%s_%ddb.png', basename, ...
337+
Mdata.(float_ids{f}).WMO_NUMBER, variables{v}, ...
338+
var2_insert{v}, press{f,d});
339+
print(f1, '-dpng', fn_png);
340+
end
341+
end
342+
end
343+
if ~per_float
344+
hold off
345+
box on;
346+
if isempty(title1) && ~ischar(title1) % i.e., []
347+
if strcmp(lgnd, 'yes')
348+
title(sprintf('Depth: %d db%s', press{f,d}, ...
349+
title_added{v}));
350+
else
351+
title(sprintf('Depth: %d db (%s)%s', press{f,d}, ...
352+
floats{f}, title_added{v}));
353+
end
354+
else
355+
title([title1, title_added{v}]);
356+
end
357+
if strcmp(lgnd,'yes')
358+
legend(float_ids,'location','eastoutside',...
359+
'AutoUpdate','off');
360+
end
361+
if ~isempty(basename)
362+
fn_png = sprintf('%s_%s%s_%ddb.png', basename, variables{v}, ...
363+
var2_insert{v}, press{f,d});
364+
print(f1, '-dpng', fn_png);
365+
end
366+
end
367+
end
368+
end

initialize_argo.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ function initialize_argo()
6969
Settings.temp_thresh = 0.2;
7070
Settings.dens_thresh = 0.03;
7171

72+
% default value for deviation from requested depth level for time series
73+
% plots - if exceeded, a warning will be issued
74+
Settings.depth_tol = 5;
75+
7276
% Settings.colormap = 'jet'; % uncomment and change as needed
7377

7478
% colors for profile plots ("range" is for individual profiles using the

0 commit comments

Comments
 (0)