diff --git a/DDB/executeCSVQuery.m b/DDB/executeCSVQuery.m new file mode 100644 index 000000000..f8496c325 --- /dev/null +++ b/DDB/executeCSVQuery.m @@ -0,0 +1,184 @@ +function result = executeCSVQuery( file, field, value) +%EXECUTECSVQUERY Alternative to executeDDBQuery, uses CSV files. +% +% Uses multiple csv files to obtain information equivalent to +% executeDDBQuery. +% +% Inputs: +% file - The csv file to query. +% +% field - Name of field to search for value. If passed in as an +% empty matrix, the entire table is returned. +% +% value - Value of field on which to restrict query. +% +% Outputs: +% result - Vector of structs, each entry representing one tuple of the +% query result. +% +% Author: Rebecca Cowley + +% +% Copyright (c) 2009, eMarine Information Infrastructure (eMII) and Integrated +% Marine Observing System (IMOS). +% All rights reserved. +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions are met: +% +% * Redistributions of source code must retain the above copyright notice, +% this list of conditions and the following disclaimer. +% * Redistributions in binary form must reproduce the above copyright +% notice, this list of conditions and the following disclaimer in the +% documentation and/or other materials provided with the distribution. +% * Neither the name of the eMII/IMOS nor the names of its contributors +% may be used to endorse or promote products derived from this software +% without specific prior written permission. +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% POSSIBILITY OF SUCH DAMAGE. +% + narginchk(3,3); + + if ~ischar(file), error('file must be a string'); end + if ~isempty(field) ... + && ~ischar(field), error('field must be a string'); end + + % in order to reduce the number of queries to the ddb (slow, we store + % each result in a global structure so that we don't perform twice the + % same query. + persistent csvStruct; + if isempty(csvStruct) + csvStruct = struct; + csvStruct.table = {}; + csvStruct.field = {}; + csvStruct.value = {}; + csvStruct.result = {}; + else + iTable = strcmpi(file, csvStruct.table); + if any(iTable) + iField = strcmpi(field, csvStruct.field); + iField = iField & iTable; + if any(iField) + iValue = strcmpi(csvStruct.value, num2str(value)); + iValue = iValue & iField; + if any(iValue) + result = csvStruct.result{iValue}; + return; + end + end + end + end + + %get location of csv files: + dirnm = readProperty('toolbox.ddb.connection'); + if isempty(dirnm) + dirnm = pwd; + end + + % complete the file name: + file = fullfile(dirnm, [file '.csv']); + + %check the file exists, if not, prompt user to select file + if exist(file,'file') == 0 + %open dialog to select a file + disp(['Deployment CSV file ' file ' not found']) + return + % Code it in.... + end + + % open the file + fid = fopen(file); + + %figure out how many columns we have: + header1 = fgetl(fid); + ncols = length(strfind(header1,',')) + 1; + fmtHeader1 = repmat('%q', 1, ncols); + header1 = textscan(header1, fmtHeader1, 'Delimiter', ',', 'CollectOutput', 1); + header1 = header1{1}; + + %build the format string + header2 = fgetl(fid); + fmt = strrep(header2, ',', ''); + + %close and re-open the file + fclose(fid); + fid = fopen(file); + + %extract all the data + data = textscan(fid, fmt, ... + 'Delimiter', ',' , ... + 'HeaderLines', 2); +% data = data{1}; + for i=1:length(data) + if isfloat(data{i}) + myData(:, i) = num2cell(data{i}); + else + myData(:, i) = data{i}; + end + end + fclose(fid); + + result = extractdata(header1, myData, field, value); + + % save result in structure + csvStruct.table{end+1} = file; + csvStruct.field{end+1} = field; + csvStruct.value{end+1} = num2str(value); + csvStruct.result{end+1} = result; +end + +function result = extractdata(header, data, field, value); +% Extract the required values from the data +%headers become field names + +if ~isempty(field) + ifield = strcmp(field,header); + if ~any(ifield) + disp(['Field ' field ' not found in the csv file']) + result = []; + return + end + + % extract the values in field: + ivalue = strcmp(value,data(:,ifield)); + data = data(ivalue,:); +end + +%look for dates and convert to matlab date format: +dateTimeFmt = 'dd/mm/yy HH:MM'; +idate = cellfun(@isempty,strfind(lower(header),'date')); +idate = find(~idate); +for a = idate + iempty = cellfun(@isempty, data(:,a)); + data(~iempty,a) = cellfun(@(x) datenum(x,dateTimeFmt), data(~iempty,a), 'Uniform', false); +% iempty = cellfun(@isempty, data(:,a)); +% cellDateStr = data{~iempty,a}; +% arrayDateNum = datenum(cellDateStr, dateTimeFmt); +% cellDateNum = num2cell(arrayDateNum); +% data{~iempty,a} = cellDateNum; +end + +%look for times and convert to matlab date format: +idate = cellfun(@isempty,strfind(lower(header),'time')); +idateTZ = cellfun(@isempty,strfind(lower(header),'timezone')); +idateTD = cellfun(@isempty,strfind(lower(header),'timedriftinstrument')); +idate = find(~idate & idateTZ & idateTD); +for a = idate + iempty = cellfun(@isempty, data(:,a)); + data(~iempty,a) = cellfun(@(x) datenum(x,dateTimeFmt), data(~iempty,a), 'Uniform', false); +end + +%make the structure: +result = cell2struct(data,header,2); + +end diff --git a/FlowManager/autoIMOSToolbox.m b/FlowManager/autoIMOSToolbox.m index 4889ae63f..be38e602e 100644 --- a/FlowManager/autoIMOSToolbox.m +++ b/FlowManager/autoIMOSToolbox.m @@ -219,7 +219,7 @@ function autoIMOSToolbox(toolboxVersion, fieldTrip, dataDir, ppChain, qcChain, e if isempty(sample_data), continue; end raw_data = preprocessManager(sample_data, 'raw', mode, true); - qc_data = preprocessManager(sample_data, 'qc', mode, true); + qc_data = preprocessManager(raw_data, 'qc', mode, true); clear sample_data; qc_data = autoQCManager(qc_data, true); diff --git a/FlowManager/importManager.m b/FlowManager/importManager.m index e32857bab..3a36969d1 100644 --- a/FlowManager/importManager.m +++ b/FlowManager/importManager.m @@ -71,7 +71,8 @@ end % If the toolbox.ddb property has been set, assume that we have a - % deployment database. Otherwise perform a manual import + % deployment database. Or if it is designated as 'csv', use a CSV file for + % import. Otherwise perform a manual import. ddb = readProperty('toolbox.ddb'); driver = readProperty('toolbox.ddb.driver'); @@ -81,10 +82,10 @@ rawFiles = {}; if ~isempty(ddb) || (~isempty(driver) && ~isempty(connection)) - [structs rawFiles] = ddbImport(auto, iMooring); + [structs rawFiles] = ddbImport(auto, iMooring, ddb); else - if auto, error('manual import cannot be automated without deployment database'); end - [structs rawFiles] = manualImport(); + if auto, error('manual import cannot be automated without deployment database'); end + [structs rawFiles] = manualImport(); end % user cancelled @@ -190,13 +191,18 @@ end end -function [sample_data rawFiles] = ddbImport(auto, iMooring) +function [sample_data rawFiles] = ddbImport(auto, iMooring, ddb) %DDBIMPORT Imports data sets using metadata retrieved from a deployment % database. % % Inputs: % auto - if true, the import process is automated, with no user % interaction. +% iMooring - Optional logical(comes with auto == true). Contains +% the logical indices to extract only the deployments +% from one mooring set of deployments. +% ddb - deployment database string attribute from +% toolboxProperties.txt % % Outputs: % sample_data - cell array containig the imported data sets, or empty @@ -304,8 +310,16 @@ rawFile = deps(k).FileName; hits = fsearch(rawFile, dataDir, 'files'); - - allFiles{k} = hits; + + % we remove any potential .pqc or .mqc files found (reserved for use + % by the toolbox) + reservedExts = {'.pqc', '.mqc'}; + for l=1:length(hits) + [~, ~, ext] = fileparts(hits{l}); + if all(~strcmp(ext, reservedExts)) + allFiles{k}{end+1} = hits{l}; + end + end end % display status dialog to highlight any discrepancies (file not found @@ -348,9 +362,8 @@ fileDisplay = fileDisplay(3:end); waitbar(k / length(deps), progress, fileDisplay); end - % import data - sample_data{end+1} = parse(deps(k), allFiles{k}, parsers, noParserPrompt, mode); + sample_data{end+1} = parse(deps(k), allFiles{k}, parsers, noParserPrompt, mode, ddb); rawFiles{ end+1} = allFiles{k}; if iscell(sample_data{end}) @@ -407,7 +420,7 @@ % close progress dialog if ~auto, close(progress); end - function sam = parse(deployment, files, parsers, noParserPrompt, mode) + function sam = parse(deployment, files, parsers, noParserPrompt, mode, ddb) %PARSE Parses a raw data file, returns a sample_data struct. % % Inputs: @@ -416,12 +429,14 @@ % parsers - Cell array of strings containing all available parsers. % noParserPrompt - Whether to prompt the user if a parser cannot be found. % mode - Toolbox data type mode ('profile' or 'timeSeries'). + % ddb - deployment database string attribute from + % toolboxProperties.txt % % Outputs: % sam - Struct containing sample data. % get the appropriate parser function - parser = getParserFunc(deployment, parsers, noParserPrompt); + parser = getParserFunc(deployment, parsers, noParserPrompt, ddb); if isnumeric(parser) error(['no parser found for instrument ' deployment.InstrumentID]); end @@ -430,7 +445,7 @@ sam = parser(files, mode); end - function parser = getParserFunc(deployment, parsers, noParserPrompt) + function parser = getParserFunc(deployment, parsers, noParserPrompt, ddb) %GETPARSERFUNC Searches for a parser function which is able to parse data % for the given deployment. % @@ -438,13 +453,20 @@ % deployment - struct containing information about the deployment. % parsers - Cell array of strings containing all available parsers. % noParserPrompt - Whether to prompt the user if a parser cannot be found. + % ddb - deployment database string attribute from + % toolboxProperties.txt % % Outputs: % parser - Function handle to the parser function, or 0 if a parser % function wasn't found. % - instrument = executeDDBQuery(... - 'Instruments', 'InstrumentID', deployment.InstrumentID); + if strcmp('csv',ddb) + instrument = executeCSVQuery(... + 'Instruments', 'InstrumentID', deployment.InstrumentID); + else + instrument = executeDDBQuery(... + 'Instruments', 'InstrumentID', deployment.InstrumentID); + end % there should be exactly one instrument if length(instrument) ~= 1 @@ -480,4 +502,4 @@ % get the parser function handle parser = getParser(parser); end -end \ No newline at end of file +end diff --git a/GUI/dataFileStatusDialog.m b/GUI/dataFileStatusDialog.m index 635c510ba..9cddce0a6 100644 --- a/GUI/dataFileStatusDialog.m +++ b/GUI/dataFileStatusDialog.m @@ -64,15 +64,30 @@ origDeployments = deployments; origFiles = files; + % get the toolbox execution mode. Values can be 'timeSeries' and 'profile'. + % If no value is set then default mode is 'timeSeries' + mode = lower(readProperty('toolbox.mode')); + deploymentDescs = genDepDescriptions(deployments, files); - % put deployments in alphabetical order + % Sort data_samples % % [B, iX] = sort(A); % => % A(iX) == B % - [deploymentDescs, iSort] = sort(deploymentDescs); + switch mode + case 'profile' + % for a profile, sort by alphabetical order + [deploymentDescs, iSort] = sort(deploymentDescs); + otherwise + % for a mooring, sort instruments by depth + [~, iSort] = sort([deployments.InstrumentDepth]); + deploymentDescs = deploymentDescs(iSort); + end + + + deployments = deployments(iSort); files = files(iSort); @@ -245,14 +260,16 @@ function fileRemoveCallback(source,ev) dep = get(depList, 'Value'); file = get(fileList, 'Value'); - files{dep}(file) = []; - - set(fileList, 'String', files{dep}, 'Value', 1); - - % update deplist view (the deployment's status may have changed) - set(depList, ... - 'Value', dep, ... - 'String', annotateDepDescriptions(deployments, deploymentDescs)); + if ~isempty(files{dep}) + files{dep}(file) = []; + + set(fileList, 'String', files{dep}, 'Value', 1); + + % update deplist view (the deployment's status may have changed) + set(depList, ... + 'Value', dep, ... + 'String', annotateDepDescriptions(deployments, deploymentDescs)); + end end function fileAddCallback(source,ev) @@ -260,11 +277,6 @@ function fileAddCallback(source,ev) % Opens a dialog, allowing the user to select a file to add to the % deployment. % - - % get the toolbox execution mode. Values can be 'timeSeries' and 'profile'. - % If no value is set then default mode is 'timeSeries' - mode = lower(readProperty('toolbox.mode')); - switch mode case 'profile' [newFile path] = uigetfile('*', 'Select Data File',... @@ -302,21 +314,35 @@ function fileAddCallback(source,ev) % deployments, suitable for use in the deployments list. % + ddb = readProperty('toolbox.ddb'); + % set values for lists - descs = {}; for k = 1:length(deployments) dep = deployments(k); - % at the very least, the instrument and file name + % at the very least, the instrument, nominal depth and file name descs{k} = dep.InstrumentID; + if ~isempty(dep.InstrumentDepth) + descs{k} = [descs{k} ' @' num2str(dep.InstrumentDepth) 'm']; + end + if isfield(dep, 'DepthTxt') + if ~isempty(dep.DepthTxt) + descs{k} = [descs{k} ' ' dep.DepthTxt]; + end + end if ~isempty(dep.FileName) descs{k} = [descs{k} ' (' dep.FileName ')']; end + % get some site information if it exists - site = executeDDBQuery('Sites', 'Site', dep.Site); + if strcmp('csv', ddb) + site = executeCSVQuery('Sites', 'Site', dep.Site); + else + site = executeDDBQuery('Sites', 'Site', dep.Site); + end if ~isempty(site) diff --git a/GUI/mainWindow.m b/GUI/mainWindow.m index 4a4863959..154cb4121 100644 --- a/GUI/mainWindow.m +++ b/GUI/mainWindow.m @@ -277,6 +277,12 @@ function mainWindow(... %set uimenu hToolsMenu = uimenu(fig, 'label', 'Tools'); if strcmpi(mode, 'timeseries') + hToolsCheckPlannedDepths = uimenu(hToolsMenu, 'label', 'Check measured against planned depths'); + hToolsCheckPlannedDepthsNonQC = uimenu(hToolsCheckPlannedDepths, 'label', 'non QC'); + hToolsCheckPlannedDepthsQC = uimenu(hToolsCheckPlannedDepths, 'label', 'QC'); + hToolsCheckPressDiffs = uimenu(hToolsMenu, 'label', 'Check pressure differences between selected instrument and nearest neighbours'); + hToolsCheckPressDiffsNonQC = uimenu(hToolsCheckPressDiffs, 'label', 'non QC'); + hToolsCheckPressDiffsQC = uimenu(hToolsCheckPressDiffs, 'label', 'QC'); hToolsLineDepth = uimenu(hToolsMenu, 'label', 'Line plot mooring''s depths'); hToolsLineDepthNonQC = uimenu(hToolsLineDepth, 'label', 'non QC'); hToolsLineDepthQC = uimenu(hToolsLineDepth, 'label', 'QC'); @@ -291,14 +297,18 @@ function mainWindow(... hToolsScatter2DCommonVarQC = uimenu(hToolsScatter2DCommonVar, 'label', 'QC'); %set menu callbacks - set(hToolsLineDepthNonQC, 'callBack', {@displayLineMooringDepth, false}); - set(hToolsLineDepthQC, 'callBack', {@displayLineMooringDepth, true}); - set(hToolsLineCommonVarNonQC, 'callBack', {@displayLineMooringVar, false}); - set(hToolsLineCommonVarQC, 'callBack', {@displayLineMooringVar, true}); - set(hToolsScatterCommonVarNonQC, 'callBack', {@displayScatterMooringVar, false, true}); - set(hToolsScatterCommonVarQC, 'callBack', {@displayScatterMooringVar, true, true}); - set(hToolsScatter2DCommonVarNonQC,'callBack', {@displayScatterMooringVar, false, false}); - set(hToolsScatter2DCommonVarQC, 'callBack', {@displayScatterMooringVar, true, false}); + set(hToolsCheckPlannedDepthsNonQC, 'callBack', {@displayCheckPlannedDepths, false}); + set(hToolsCheckPlannedDepthsQC, 'callBack', {@displayCheckPlannedDepths, true}); + set(hToolsCheckPressDiffsNonQC, 'callBack', {@displayCheckPressDiffs, false}); + set(hToolsCheckPressDiffsQC, 'callBack', {@displayCheckPressDiffs, true}); + set(hToolsLineDepthNonQC, 'callBack', {@displayLineMooringDepth, false}); + set(hToolsLineDepthQC, 'callBack', {@displayLineMooringDepth, true}); + set(hToolsLineCommonVarNonQC, 'callBack', {@displayLineMooringVar, false}); + set(hToolsLineCommonVarQC, 'callBack', {@displayLineMooringVar, true}); + set(hToolsScatterCommonVarNonQC, 'callBack', {@displayScatterMooringVar, false, true}); + set(hToolsScatterCommonVarQC, 'callBack', {@displayScatterMooringVar, true, true}); + set(hToolsScatter2DCommonVarNonQC, 'callBack', {@displayScatterMooringVar, false, false}); + set(hToolsScatter2DCommonVarQC, 'callBack', {@displayScatterMooringVar, true, false}); else hToolsLineCastVar = uimenu(hToolsMenu, 'label', 'Line plot profile variables'); hToolsLineCastVarNonQC = uimenu(hToolsLineCastVar, 'label', 'non QC'); @@ -467,6 +477,31 @@ function zoomPostCallback(source,ev) end %% Menu callback + function displayCheckPressDiffs(source,ev, isQC) + %DISPLAYLINEPRESSDIFFS opens a new window where all the PRES/PRES_REL + %values for instruments adjacent to the current instrument are + %displayed with the differences between these instrument pressures + % + %check for pressure + iSampleMenu = get(sampleMenu, 'Value'); + iPRES_REL = getVar(sample_data{iSampleMenu}.variables, 'PRES_REL'); + iPRES = getVar(sample_data{iSampleMenu}.variables, 'PRES'); + if iPRES_REL == 0 && iPRES == 0 + sampleMenuStrings = get(sampleMenu, 'String'); + disp(['No pressure data for ' sampleMenuStrings{iSampleMenu}]) + return + end + + checkMooringPresDiffs(sample_data, iSampleMenu, isQC, false, ''); + end + + function displayCheckPlannedDepths(source,ev, isQC) + %DISPLAYCHECKPLANNEDDEPTHS Opens a new window where the actual + %depths recorded are compared to the planned depths. + % + checkMooringPlannedDepths(sample_data, isQC, false, ''); + end + function displayLineMooringDepth(source,ev, isQC) %DISPLAYLINEMOORINGDEPTH Opens a new window where all the nominal depths and %actual/computed depths from intruments on the mooring are line-plotted. diff --git a/GUI/startDialog.m b/GUI/startDialog.m index 1b0d27f6b..0b9817b55 100644 --- a/GUI/startDialog.m +++ b/GUI/startDialog.m @@ -1,4 +1,4 @@ -function [fieldTrip dataDir] = startDialog(mode) +function [fieldTrip dataDir] = startDialog(mode, isCSV) %STARTDIALOG Displays a dialog prompting the user to select a Field Trip % and a directory which contains raw data files. % @@ -11,6 +11,7 @@ % Input: % % mode - String, toolox execution mode can be 'profile' or 'timeSeries'. +% isCSV - optional boolean (default = false). True if importing from csv files. % % Outputs: % @@ -52,7 +53,11 @@ % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. % - narginchk(1,1); + narginchk(1,2); + + if nargin == 1 + isCSV = false; + end dateFmt = readProperty('toolbox.dateFormat'); @@ -77,7 +82,11 @@ if isnan(highDate), highDate = now_utc; end % retrieve all field trip IDs; they are displayed as a drop down menu - fieldTrips = executeDDBQuery('FieldTrip', [], []); + if isCSV + fieldTrips = executeCSVQuery('FieldTrip', [], []); + else + fieldTrips = executeDDBQuery('FieldTrip', [], []); + end if isempty(fieldTrips), error('No field trip entries in DDB'); end diff --git a/Graph/TimeSeries/setTimeSerieColorbarContextMenu.m b/Graph/TimeSeries/setTimeSerieColorbarContextMenu.m index 6391e6611..8892cbcbc 100644 --- a/Graph/TimeSeries/setTimeSerieColorbarContextMenu.m +++ b/Graph/TimeSeries/setTimeSerieColorbarContextMenu.m @@ -105,40 +105,44 @@ uimenu(mainItem2, 'Label', 'manual', 'Callback', {@cbCLimRange, 'manual', var.data}); case 'PERG' % percentages - colormap(jet); + colormap(parula); cbCLimRange('', '', 'percent [0; 100]', var.data); % Define a context menu hMenu = uicontextmenu; % Define callbacks for context menu items that change linestyle - hcb11 = 'colormap(jet)'; + hcb11 = 'colormap(parula)'; + hcb12 = 'colormap(jet)'; hcb13 = 'colormapeditor'; % Define the context menu items and install their callbacks mainItem1 = uimenu(hMenu, 'Label', 'Colormaps'); - uimenu(mainItem1, 'Label', 'jet (default)', 'Callback', hcb11); - uimenu(mainItem1, 'Label', 'other', 'Callback', hcb13); + uimenu(mainItem1, 'Label', 'parula (default)', 'Callback', hcb11); + uimenu(mainItem1, 'Label', 'jet', 'Callback', hcb12); + uimenu(mainItem1, 'Label', 'other', 'Callback', hcb13); mainItem2 = uimenu(hMenu, 'Label', 'Color range'); uimenu(mainItem2, 'Label', 'percent [0; 100] (default)', 'Callback', {@cbCLimRange, 'percent [0; 100]', var.data}); uimenu(mainItem2, 'Label', 'manual', 'Callback', {@cbCLimRange, 'manual', var.data}); case {'CSPD', 'VDEN', 'VDEV', 'VDEP', 'VDES'} % [0; oo[ paremeters - colormap(jet); + colormap(parula); cbCLimRange('', '', 'auto from 0', var.data); % Define a context menu hMenu = uicontextmenu; % Define callbacks for context menu items that change linestyle + hcb11 = 'colormap(parula)'; hcb12 = 'colormap(jet)'; hcb13 = 'colormapeditor'; % Define the context menu items and install their callbacks mainItem1 = uimenu(hMenu, 'Label', 'Colormaps'); - uimenu(mainItem1, 'Label', 'jet (default)', 'Callback', hcb12); - uimenu(mainItem1, 'Label', 'other', 'Callback', hcb13); + uimenu(mainItem1, 'Label', 'parula (default)', 'Callback', hcb11); + uimenu(mainItem1, 'Label', 'jet', 'Callback', hcb12); + uimenu(mainItem1, 'Label', 'other', 'Callback', hcb13); mainItem2 = uimenu(hMenu, 'Label', 'Color range'); uimenu(mainItem2, 'Label', 'full', 'Callback', {@cbCLimRange, 'full', var.data}); @@ -157,15 +161,17 @@ hMenu = uicontextmenu; % Define callbacks for context menu items that change linestyle - hcb12 = 'load(''jet_w.mat'', ''-mat'', ''jet_w''); colormap(jet_w)'; + hcb11 = 'load(''jet_w.mat'', ''-mat'', ''jet_w''); colormap(jet_w)'; + hcb12 = 'colormap(parula)'; hcb13 = 'colormap(jet)'; hcb14 = 'colormapeditor'; % Define the context menu items and install their callbacks mainItem1 = uimenu(hMenu, 'Label', 'Colormaps'); - uimenu(mainItem1, 'Label', 'jet_w (default)', 'Callback', hcb12); - uimenu(mainItem1, 'Label', 'jet', 'Callback', hcb13); - uimenu(mainItem1, 'Label', 'other', 'Callback', hcb14); + uimenu(mainItem1, 'Label', 'jet_w (default)', 'Callback', hcb11); + uimenu(mainItem1, 'Label', 'parula', 'Callback', hcb12); + uimenu(mainItem1, 'Label', 'jet', 'Callback', hcb13); + uimenu(mainItem1, 'Label', 'other', 'Callback', hcb14); mainItem2 = uimenu(hMenu, 'Label', 'Color range'); uimenu(mainItem2, 'Label', 'full', 'Callback', {@cbCLimRange, 'full', var.data}); @@ -175,22 +181,24 @@ uimenu(mainItem2, 'Label', 'manual', 'Callback', {@cbCLimRange, 'manual', var.data}); otherwise - colormap(jet); + colormap(parula); cbCLimRange('', '', 'full', var.data); % Define a context menu hMenu = uicontextmenu; % Define callbacks for context menu items that change linestyle - hcb11 = 'colormap(r_b)'; + hcb11 = 'colormap(parula)'; hcb12 = 'colormap(jet)'; - hcb13 = 'colormapeditor'; + hcb13 = 'colormap(r_b)'; + hcb14 = 'colormapeditor'; % Define the context menu items and install their callbacks mainItem1 = uimenu(hMenu, 'Label', 'Colormaps'); - uimenu(mainItem1, 'Label', 'r_b', 'Callback', hcb11); - uimenu(mainItem1, 'Label', 'jet (default)', 'Callback', hcb12); - uimenu(mainItem1, 'Label', 'other', 'Callback', hcb13); + uimenu(mainItem1, 'Label', 'parula (default)', 'Callback', hcb11); + uimenu(mainItem1, 'Label', 'jet', 'Callback', hcb12); + uimenu(mainItem1, 'Label', 'r_b', 'Callback', hcb13); + uimenu(mainItem1, 'Label', 'other', 'Callback', hcb14); mainItem2 = uimenu(hMenu, 'Label', 'Color range'); uimenu(mainItem2, 'Label', 'full (default)', 'Callback', {@cbCLimRange, 'full', var.data}); diff --git a/Graph/checkMooringPlannedDepths.m b/Graph/checkMooringPlannedDepths.m new file mode 100644 index 000000000..8dcaa9d48 --- /dev/null +++ b/Graph/checkMooringPlannedDepths.m @@ -0,0 +1,331 @@ +function checkMooringPlannedDepths(sample_data, isQC, saveToFile, exportDir) +%CHECKMOORINGPLANNEDDEPTHS Opens a new window where the DEPTH +% variable of all intruments is plotted and compared to the +% planned depth. +% +% Inputs: +% sample_data - cell array of structs containing the entire data set and dimension data. +% +% varName - string containing the IMOS code for requested parameter. +% +% isQC - logical to plot only good data or not. +% +% saveToFile - logical to save the plot on disk or not. +% +% exportDir - string containing the destination folder to where the +% plot is saved on disk. +% +% Author: Rebecca Cowley +% + +% +% Copyright (c) 2009, eMarine Information Infrastructure (eMII) and Integrated +% Marine Observing System (IMOS). +% All rights reserved. +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions are met: +% +% * Redistributions of source code must retain the above copyright notice, +% this list of conditions and the following disclaimer. +% * Redistributions in binary form must reproduce the above copyright +% notice, this list of conditions and the following disclaimer in the +% documentation and/or other materials provided with the distribution. +% * Neither the name of the eMII/IMOS nor the names of its contributors +% may be used to endorse or promote products derived from this software +% without specific prior written permission. +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% POSSIBILITY OF SUCH DAMAGE. +% +narginchk(4,4); + +if ~iscell(sample_data), error('sample_data must be a cell array'); end +if ~islogical(isQC), error('isQC must be a logical'); end +if ~islogical(saveToFile), error('saveToFile must be a logical'); end +if ~ischar(exportDir), error('exportDir must be a string'); end + +varTitle = imosParameters('DEPTH', 'long_name'); +varUnit = imosParameters('DEPTH', 'uom'); + +stringQC = 'non QC'; +if isQC, stringQC = 'QC'; end + +%plot depth information +monitorRec = get(0,'MonitorPosition'); +xResolution = monitorRec(:, 3)-monitorRec(:, 1); +iBigMonitor = xResolution == max(xResolution); +if sum(iBigMonitor)==2, iBigMonitor(2) = false; end % in case exactly same monitors + +title = [sample_data{1}.deployment_code ' mooring planned depth vs measured depth ' stringQC '''d good ' varTitle]; + +%extract the essential data and +%sort instruments by depth +lenSampleData = length(sample_data); +instrumentDesc = cell(lenSampleData, 1); +hLineVar = nan(lenSampleData, 1); +metaDepth = nan(lenSampleData, 1); +xMin = nan(lenSampleData, 1); +xMax = nan(lenSampleData, 1); +dataVar = nan(lenSampleData,800000); +timeVar = dataVar; +isPlottable = false; + +backgroundColor = [0.75 0.75 0.75]; + +for i=1:lenSampleData + %only look at instruments with pressure + iPresRel = getVar(sample_data{i}.variables, 'PRES_REL'); + iPres = getVar(sample_data{i}.variables, 'PRES'); + if (iPresRel==0 && iPres==0) + continue; + end + if iPresRel + data = sample_data{i}.variables{iPresRel}.data; + else + data = sample_data{i}.variables{iPres}.data - 14.7*0.689476; % let's apply SeaBird's atmospheric correction + iPresRel = iPres; + end + + iTime = getVar(sample_data{i}.dimensions, 'TIME'); + time = sample_data{i}.dimensions{iTime}.data; + + %calculate depth + iLat = getVar(sample_data{i}.variables, 'LATITUDE'); + if isempty(iLat) + error(['Depth calculation imposible: no latitude documented for ' sample_data{i}.toolbox_input_file ... + ' serial number ' sample_data{i}.instrument_serial_number]); + end + data = -gsw_z_from_p(data, sample_data{i}.variables{iLat}.data); + + iGood = true(size(data)); + + if isQC + %get time and var QC information + timeFlags = sample_data{i}.dimensions{iTime}.flags; + presFlags = sample_data{i}.variables{iPresRel}.flags; + + iGood = (timeFlags == 0 | timeFlags == 1 | timeFlags == 2) ... + & (presFlags == 1 | presFlags == 2); + end + + if all(~iGood) && isQC + fprintf('%s\n', ['Warning : in ' sample_data{i}.toolbox_input_file ... + ', there is not any pressure data with good flags.']); + continue; + else + isPlottable = true; + end + + data = data(iGood); + time = time(iGood); + + %save the data into a holding matrix so we don't have to loop over the + %sample_data matrix again. + dataVar(i,1:length(data)) = data; + timeVar(i,1:length(time)) = time; + + if ~isempty(sample_data{i}.meta.depth) + metaDepth(i) = sample_data{i}.meta.depth; + elseif ~isempty(sample_data{i}.instrument_nominal_depth) + metaDepth(i) = sample_data{i}.instrument_nominal_depth; + else + metaDepth(i) = NaN; + end + + xMin(i) = min(time); + xMax(i) = max(time); + % instrument description + if ~isempty(strtrim(sample_data{(i)}.instrument)) + instrumentDesc{i} = sample_data{(i)}.instrument; + elseif ~isempty(sample_data{(i)}.toolbox_input_file) + [~, instrumentDesc{i}] = fileparts(sample_data{(i)}.toolbox_input_file); + end + + instrumentSN = ''; + if ~isempty(strtrim(sample_data{(i)}.instrument_serial_number)) + instrumentSN = [' - ' sample_data{(i)}.instrument_serial_number]; + end + + instrumentDesc{i} = [strrep(instrumentDesc{i}, '_', ' ') ' (' num2str(metaDepth((i))) 'm' instrumentSN ')']; +end + +if ~isPlottable + return; +end + +%only look at indexes with pres_rel +[metaDepth, iSort] = sort(metaDepth); +dataVar = dataVar(iSort,:); +timeVar = timeVar(iSort,:); +sample_data = sample_data(iSort); +instrumentDesc = instrumentDesc(iSort); +%delete non-pressure instrument information +dataVarTmp = dataVar; +dataVarTmp(isnan(dataVar)) = 0; +ibad = sum(dataVarTmp,2)==0; +metaDepth(ibad) = []; +sample_data(ibad) = []; +dataVar(ibad,:) = []; +timeVar(ibad,:) = []; +instrumentDesc(ibad) = []; +xMin = min(xMin); +xMax = max(xMax); + +instrumentDesc = [{'Make Model (nominal depth - instrument SN)'}; instrumentDesc]; +hLineVar(1) = 0; + +%now plot all the calculated depths on one plot to choose region for comparison: +%plot +fileName = genIMOSFileName(sample_data{1}, 'png'); +visible = 'on'; +if saveToFile, visible = 'off'; end +hFigPress = figure(... + 'Name', title, ... + 'NumberTitle','off', ... + 'Visible', visible, ... + 'OuterPosition', [0, 0, monitorRec(iBigMonitor, 3), monitorRec(iBigMonitor, 4)]); + +hAxPress = subplot(2,1,1,'Parent', hFigPress); +hAxDepthDiff = subplot(2,1,2,'Parent', hFigPress); + +%depth plot for selecting region to compare depth to planned depth +set(hAxPress, 'YDir', 'reverse') +set(get(hAxPress, 'XLabel'), 'String', 'Time'); +set(get(hAxPress, 'YLabel'), 'String', ['DEPTH (' varUnit ')'], 'Interpreter', 'none'); +set(get(hAxPress, 'Title'), 'String', 'Depth', 'Interpreter', 'none'); +set(hAxPress, 'XTick', (xMin:(xMax-xMin)/4:xMax)); +set(hAxPress, 'XLim', [xMin, xMax]); +hold(hAxPress, 'on'); + +%Actual depth minus planned depth +set(get(hAxDepthDiff, 'XLabel'), 'String', 'Planned Depth (m)'); +set(get(hAxDepthDiff, 'YLabel'), 'String', ['Actual Depth - Planned Depth (' varUnit ')'], 'Interpreter', 'none'); +set(get(hAxDepthDiff, 'Title'), 'String', ... + ['Differences from planned depth for ' sample_data{1}.meta.site_name] , 'Interpreter', 'none'); +hold(hAxDepthDiff, 'on'); +grid(hAxDepthDiff, 'on'); + +% set background to be grey +set(hAxPress, 'Color', backgroundColor); +set(hAxDepthDiff, 'Color', backgroundColor); + +%color map +lenMetaDepth = length(metaDepth); +try + defaultColormapFh = str2func(readProperty('visualQC.defaultColormap')); + cMap = colormap(hAxPress, defaultColormapFh(lenMetaDepth)); +catch e + cMap = colormap(hAxPress, parula(lenMetaDepth)); +end +% reverse the colorbar as we want surface instruments with warmer colors +cMap = flipud(cMap); + +% dummy entry for first entry in legend +hLineVar(1) = plot(hAxPress, 0, 0, 'Color', backgroundColor, 'Visible', 'off'); % color grey same as background (invisible) + +%now plot the data:Have to do it one at a time to get the colors right.. +for i = 1:lenMetaDepth + hLineVar(i+1) = line(timeVar(i,:), ... + dataVar(i,:), ... + 'Color', cMap(i,:),... + 'LineStyle', '-',... + 'Parent', hAxPress); +end + +% Let's redefine properties after pcolor to make sure grid lines appear +% above color data and XTick and XTickLabel haven't changed +set(hAxPress, ... + 'XTick', (xMin:(xMax-xMin)/4:xMax), ... + 'XGrid', 'on', ... + 'YGrid', 'on', ... + 'Layer', 'top'); + +if isPlottable + datetick(hAxPress, 'x', 'dd-mm-yy HH:MM:SS', 'keepticks'); + + % we try to split the legend, maximum 3 columns + fontSizeAx = get(hAxPress,'FontSize'); + fontSizeLb = get(get(hAxPress,'XLabel'),'FontSize'); + xscale = 0.9; + if numel(instrumentDesc) < 4 + nCols = 1; + elseif numel(instrumentDesc) < 8 + nCols = 2; + else + nCols = 3; + fontSizeAx = fontSizeAx - 1; + xscale = 0.75; + end + hYBuffer = 1.1 * (2*(fontSizeAx + fontSizeLb)); + hLegend = legendflex(hAxPress, instrumentDesc,... + 'anchor', [6 2], ... + 'buffer', [0 -hYBuffer], ... + 'ncol', nCols,... + 'FontSize', fontSizeAx,... + 'xscale',xscale); + posAx = get(hAxPress, 'Position'); + set(hLegend, 'Units', 'Normalized', 'color', backgroundColor); + + % for some reason this call brings everything back together while it + % shouldn't have moved previously anyway... + set(hAxPress, 'Position', posAx); +end + +%Ask for the user to select the region they would like to use for +%comparison +%This could be done better, with more finesse - could allow zooming in +%before going straight to the time period selection. For now, this will do. +hMsgbox = msgbox('Select the time period for comparison using the mouse', 'Time Period Selection', 'help', 'modal'); +uiwait(hMsgbox); + +%select the area to use for comparison +[x,y] = select_points(hAxPress); + +%now plot the difference from planned depth data: +iGood = timeVar >= x(1) & timeVar <= x(2); +dataVar(~iGood) = NaN; +minDep = min(dataVar,[],2); + +hLineVar2 = scatter(hAxDepthDiff, ... + metaDepth, ... + minDep - metaDepth, ... + 15, ... + cMap, ... + 'filled'); + +instrumentDesc(1) = []; +text(metaDepth + 1, (minDep - metaDepth), instrumentDesc, ... + 'Parent', hAxDepthDiff) + +if isPlottable + if saveToFile + % ensure the printed version is the same whatever the screen used. + set(hFigPress, 'PaperPositionMode', 'manual'); + set(hFigPress, 'PaperType', 'A4', 'PaperOrientation', 'landscape', 'PaperUnits', 'normalized', 'PaperPosition', [0, 0, 1, 1]); + + % preserve the color scheme + set(hFigPress, 'InvertHardcopy', 'off'); + + fileName = strrep(fileName, '_PARAM_', ['_', varName, '_']); % IMOS_[sub-facility_code]_[site_code]_FV01_[deployment_code]_[PLOT-TYPE]_[PARAM]_C-[creation_date].png + fileName = strrep(fileName, '_PLOT-TYPE_', '_LINE_'); + + % use hardcopy as a trick to go faster than print. + % opengl (hardware or software) should be supported by any platform and go at least just as + % fast as zbuffer. With hardware accelaration supported, could even go a + % lot faster. + imwrite(hardcopy(hFigPress, '-dopengl'), fullfile(exportDir, fileName), 'png'); + close(hFigPress); + end +end + +end \ No newline at end of file diff --git a/Graph/checkMooringPresDiffs.m b/Graph/checkMooringPresDiffs.m new file mode 100644 index 000000000..a12bbef7f --- /dev/null +++ b/Graph/checkMooringPresDiffs.m @@ -0,0 +1,347 @@ +function checkMooringPresDiffs(sample_data, iSampleMenu, isQC, saveToFile, exportDir) +%CHECKMOORINGPRESDIFFS Opens a new window where the pressure +% variable collected by the selected intrument is plotted, and the difference +% in pressure from this instrument to adjacent instruments is plotted. +% +% Inputs: +% sample_data - cell array of structs containing the entire data set and dimension data. +% +% iSampleMenu - current Value of sampleMenu. +% +% isQC - logical to plot only good data or not. +% +% saveToFile - logical to save the plot on disk or not. +% +% exportDir - string containing the destination folder to where the +% plot is saved on disk. +% +% Author: Rebecca Cowley +% + +% +% Copyright (c) 2009, eMarine Information Infrastructure (eMII) and Integrated +% Marine Observing System (IMOS). +% All rights reserved. +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions are met: +% +% * Redistributions of source code must retain the above copyright notice, +% this list of conditions and the following disclaimer. +% * Redistributions in binary form must reproduce the above copyright +% notice, this list of conditions and the following disclaimer in the +% documentation and/or other materials provided with the distribution. +% * Neither the name of the eMII/IMOS nor the names of its contributors +% may be used to endorse or promote products derived from this software +% without specific prior written permission. +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% POSSIBILITY OF SUCH DAMAGE. +% +narginchk(5,5); + +if ~iscell(sample_data), error('sample_data must be a cell array'); end +if ~islogical(isQC), error('isQC must be a logical'); end +if ~islogical(saveToFile), error('saveToFile must be a logical'); end +if ~ischar(exportDir), error('exportDir must be a string'); end + +presRelCode = 'PRES_REL'; +presCode = 'PRES'; +varTitle = imosParameters(presRelCode, 'long_name'); +varUnit = imosParameters(presRelCode, 'uom'); + +stringQC = 'non QC'; +if isQC, stringQC = 'QC'; end + +%plot depth information +monitorRec = get(0,'MonitorPosition'); +xResolution = monitorRec(:, 3)-monitorRec(:, 1); +iBigMonitor = xResolution == max(xResolution); +if sum(iBigMonitor)==2, iBigMonitor(2) = false; end % in case exactly same monitors + +title = [sample_data{1}.deployment_code ' mooring pressure differences ' stringQC '''d ' varTitle]; + +%sort instruments by depth +lenSampleData = length(sample_data); +instrumentDesc = cell(lenSampleData, 1); +metaDepth = nan(lenSampleData, 1); +xMin = nan(lenSampleData, 1); +xMax = nan(lenSampleData, 1); +iPresRel = nan(lenSampleData, 1); +iPres = nan(lenSampleData, 1); +for i=1:lenSampleData + if ~isempty(sample_data{i}.meta.depth) + metaDepth(i) = sample_data{i}.meta.depth; + elseif ~isempty(sample_data{i}.instrument_nominal_depth) + metaDepth(i) = sample_data{i}.instrument_nominal_depth; + else + metaDepth(i) = NaN; + end + + % instrument description + if ~isempty(strtrim(sample_data{i}.instrument)) + instrumentDesc{i} = sample_data{i}.instrument; + elseif ~isempty(sample_data{i}.toolbox_input_file) + [~, instrumentDesc{i}] = fileparts(sample_data{i}.toolbox_input_file); + end + + instrumentSN = ''; + if ~isempty(strtrim(sample_data{i}.instrument_serial_number)) + instrumentSN = [' - ' sample_data{i}.instrument_serial_number]; + end + + instrumentDesc{i} = [strrep(instrumentDesc{i}, '_', ' ') ' (' num2str(metaDepth(i)) 'm' instrumentSN ')']; + + iTime = getVar(sample_data{i}.dimensions, 'TIME'); + %check for pressure + iPresRel(i) = getVar(sample_data{i}.variables, presRelCode); + iPres(i) = getVar(sample_data{i}.variables, presCode); + + xMin(i) = min(sample_data{i}.dimensions{iTime}.data); + xMax(i) = max(sample_data{i}.dimensions{iTime}.data); +end +%only look at indexes with pres_rel or pres +[metaDepth, iSort] = sort(metaDepth); +sample_data = sample_data(iSort); +instrumentDesc = instrumentDesc(iSort); +iPresRel = iPresRel(iSort); +iPres = iPres(iSort); + +iSort(iPresRel==0 & iPres==0) = []; +metaDepth(iPresRel==0 & iPres==0) = []; +instrumentDesc(iPresRel==0 & iPres==0) = []; +sample_data(iPresRel==0 & iPres==0) = []; + +xMin = min(xMin); +xMax = max(xMax); + +%first find the instrument of interest: +iCurrSam = find(iSort == iSampleMenu); + +hLineVar = nan(length(metaDepth), 1); +hLineVar2 = hLineVar; + +isPlottable = false; + +backgroundColor = [0.75 0.75 0.75]; + +%plot +fileName = genIMOSFileName(sample_data{iCurrSam}, 'png'); +visible = 'on'; +if saveToFile, visible = 'off'; end +hFigPressDiff = figure(... + 'Name', title, ... + 'NumberTitle','off', ... + 'Visible', visible, ... + 'OuterPosition', [0, 0, monitorRec(iBigMonitor, 3), monitorRec(iBigMonitor, 4)]); + +%pressure plot +hAxPress = subplot(2,1,1,'Parent', hFigPressDiff); +set(hAxPress, 'YDir', 'reverse') +set(get(hAxPress, 'XLabel'), 'String', 'Time'); +set(get(hAxPress, 'YLabel'), 'String', [presRelCode ' (' varUnit ')'], 'Interpreter', 'none'); +set(get(hAxPress, 'Title'), 'String', varTitle, 'Interpreter', 'none'); +set(hAxPress, 'XTick', (xMin:(xMax-xMin)/4:xMax)); +set(hAxPress, 'XLim', [xMin, xMax]); +hold(hAxPress, 'on'); + +%Pressure diff plot +hAxPressDiff = subplot(2,1,2,'Parent', hFigPressDiff); +set(get(hAxPressDiff, 'XLabel'), 'String', 'Time'); +set(get(hAxPressDiff, 'YLabel'), 'String', [presRelCode ' (' varUnit ')'], 'Interpreter', 'none'); +set(get(hAxPressDiff, 'Title'), 'String', ... + ['Pressure Differences from ' instrumentDesc{iCurrSam} ' (in black above)'] , 'Interpreter', 'none'); +set(hAxPressDiff, 'XTick', (xMin:(xMax-xMin)/4:xMax)); +set(hAxPressDiff, 'XLim', [xMin, xMax]); +hold(hAxPressDiff, 'on'); + +linkaxes([hAxPressDiff,hAxPress],'x') + +%zero line +line([xMin, xMax], [0, 0], 'Color', 'black'); + +%now plot the data of interest: +iCurrTime = getVar(sample_data{iCurrSam}.dimensions, 'TIME'); +curSamTime = sample_data{iCurrSam}.dimensions{iCurrTime}.data; + +iCurrPresRel = getVar(sample_data{iCurrSam}.variables, presRelCode); +iCurrPres = getVar(sample_data{iCurrSam}.variables, presCode); +if iCurrPresRel + curSamPresRel = sample_data{iCurrSam}.variables{iCurrPresRel}.data; +else + curSamPresRel = sample_data{iCurrSam}.variables{iCurrPres}.data - 14.7*0.689476; % let's apply SeaBird's atmospheric correction + iCurrPresRel = iCurrPres; +end + +iGood = true(size(curSamPresRel)); +if isQC + %get time and var QC information + timeFlags = sample_data{iCurrSam}.dimensions{iCurrTime}.flags; + varFlags = sample_data{iCurrSam}.variables{iCurrPresRel}.flags; + + iGood = (timeFlags == 0 | timeFlags == 1 | timeFlags == 2) & (varFlags == 1 | varFlags == 2); +end + +curSamTime(~iGood) = NaN; +curSamPresRel(~iGood) = NaN; + +%now get the adjacent instruments based on planned depth (up to 4 nearest) +metaDepthCurrSam = metaDepth(iCurrSam); +[~, iOthers] = sort(abs(metaDepthCurrSam - metaDepth)); +nOthers = length(iOthers); +nOthersMax = 5; % includes current sample +if nOthers > nOthersMax + iOthers = iOthers(1:nOthersMax); + nOthers = nOthersMax; +end + +%color map +% no need to reverse the colorbar since instruments are plotted from +% nearest (blue) to farthest (yellow) +try + defaultColormapFh = str2func(readProperty('visualQC.defaultColormap')); + cMap = colormap(hAxPress, defaultColormapFh(nOthers - 1)); +catch e + cMap = colormap(hAxPress, parula(nOthers - 1)); +end +% current sample is black +cMap = [0, 0, 0; cMap]; + +%now add the other data: +for i=1:nOthers + %look for time and relevant variable + iOtherTime = getVar(sample_data{iOthers(i)}.dimensions, 'TIME'); + otherSamTime = sample_data{iOthers(i)}.dimensions{iOtherTime}.data; + + iOtherPresRel = getVar(sample_data{iOthers(i)}.variables, presRelCode); + iOtherPres = getVar(sample_data{iOthers(i)}.variables, presCode); + if iOtherPresRel + otherSamPresRel = sample_data{iOthers(i)}.variables{iOtherPresRel}.data; + else + otherSamPresRel = sample_data{iOthers(i)}.variables{iOtherPres}.data - 14.7*0.689476; % let's apply SeaBird's atmospheric correction + iOtherPresRel = iOtherPres; + end + + iGood = true(size(otherSamPresRel)); + if isQC + %get time and var QC information + timeFlags = sample_data{iOthers(i)}.dimensions{iOtherTime}.flags; + varFlags = sample_data{iOthers(i)}.variables{iOtherPresRel}.flags; + + iGood = (timeFlags == 0 | timeFlags == 1 | timeFlags == 2) & (varFlags == 1 | varFlags == 2); + end + + if all(~iGood) && isQC + fprintf('%s\n', ['Warning : in ' sample_data{iOthers(i)}.toolbox_input_file ... + ', there is not any pressure data with good flags.']); + continue; + else + isPlottable = true; + + otherSamTime(~iGood) = []; + otherSamPresRel(~iGood) = []; + + %add pressure to the pressure plot + hLineVar(iOthers(i)) = line(otherSamTime, ... + otherSamPresRel, ... + 'Color', cMap(i, :), ... + 'LineStyle', '-','Parent',hAxPress); + + %now put the data on the same timebase as the instrument of + %interest + newdat = interp1(otherSamTime,otherSamPresRel,curSamTime); + pdiff = curSamPresRel - newdat; + + hLineVar2(iOthers(i)) = line(curSamTime, ... + pdiff, ... + 'Color', cMap(i, :), ... + 'LineStyle', '-','Parent',hAxPressDiff); + + % set background to be grey + set(hAxPress, 'Color', backgroundColor) + set(hAxPressDiff, 'Color', backgroundColor) + end +end + +% Let's redefine properties after pcolor to make sure grid lines appear +% above color data and XTick and XTickLabel haven't changed +set(hAxPress, ... + 'XTick', (xMin:(xMax-xMin)/4:xMax), ... + 'XGrid', 'on', ... + 'YGrid', 'on', ... + 'Layer', 'top'); + +set(hAxPressDiff, ... + 'XTick', (xMin:(xMax-xMin)/4:xMax), ... + 'XGrid', 'on', ... + 'YGrid', 'on', ... + 'Layer', 'top'); + +if isPlottable + iNan = isnan(hLineVar); + if any(iNan) + hLineVar(iNan) = []; + instrumentDesc(iNan) = []; + end + iOthers = iOthers - (min(iOthers)-1); + + datetick(hAxPressDiff, 'x', 'dd-mm-yy HH:MM:SS', 'keepticks'); + datetick(hAxPress, 'x', 'dd-mm-yy HH:MM:SS', 'keepticks'); + + % we try to split the legend, maximum 3 columns + fontSizeAx = get(hAxPress,'FontSize'); + fontSizeLb = get(get(hAxPress,'XLabel'),'FontSize'); + xscale = 0.9; + if numel(instrumentDesc) < 4 + nCols = 1; + elseif numel(instrumentDesc) < 8 + nCols = 2; + else + nCols = 3; + fontSizeAx = fontSizeAx - 1; + xscale = 0.75; + end + hYBuffer = 1.1 * (2*(fontSizeAx + fontSizeLb)); + hLegend = legendflex(hAxPress, instrumentDesc(iOthers),... + 'anchor', [6 2], ... + 'buffer', [0 -hYBuffer], ... + 'ncol', nCols,... + 'FontSize', fontSizeAx,... + 'xscale',xscale); + posAx = get(hAxPress, 'Position'); + set(hLegend, 'Units', 'Normalized', 'color', backgroundColor); + + % for some reason this call brings everything back together while it + % shouldn't have moved previously anyway... + set(hAxPress, 'Position', posAx); + + if saveToFile + % ensure the printed version is the same whatever the screen used. + set(hFigPressDiff, 'PaperPositionMode', 'manual'); + set(hFigPressDiff, 'PaperType', 'A4', 'PaperOrientation', 'landscape', 'PaperUnits', 'normalized', 'PaperPosition', [0, 0, 1, 1]); + + % preserve the color scheme + set(hFigPressDiff, 'InvertHardcopy', 'off'); + + fileName = strrep(fileName, '_PARAM_', ['_', varName, '_']); % IMOS_[sub-facility_code]_[site_code]_FV01_[deployment_code]_[PLOT-TYPE]_[PARAM]_C-[creation_date].png + fileName = strrep(fileName, '_PLOT-TYPE_', '_LINE_'); + + % use hardcopy as a trick to go faster than print. + % opengl (hardware or software) should be supported by any platform and go at least just as + % fast as zbuffer. With hardware accelaration supported, could even go a + % lot faster. + imwrite(hardcopy(hFigPressDiff, '-dopengl'), fullfile(exportDir, fileName), 'png'); + close(hFigPressDiff); + end +end + +end \ No newline at end of file diff --git a/Graph/graphDepthProfile.m b/Graph/graphDepthProfile.m index 604451c16..693f0495d 100644 --- a/Graph/graphDepthProfile.m +++ b/Graph/graphDepthProfile.m @@ -161,13 +161,15 @@ xLabel = [labels{1} uom]; set(get(graphs(k), 'XLabel'), 'String', xLabel, 'Interpreter', 'none'); - % set y label - try uom = [' (' imosParameters(labels{2}, 'uom') ')']; - catch e, uom = ''; + % set y label for the first plot + if k==1 + try uom = [' (' imosParameters(labels{2}, 'uom') ')']; + catch e, uom = ''; + end + yLabel = [labels{2} uom]; + if length(yLabel) > 20, yLabel = [yLabel(1:17) '...']; end + set(get(graphs(k), 'YLabel'), 'String', yLabel, 'Interpreter', 'none'); end - yLabel = [labels{2} uom]; - if length(yLabel) > 20, yLabel = [yLabel(1:17) '...']; end - set(get(graphs(k), 'YLabel'), 'String', yLabel, 'Interpreter', 'none'); if sample_data.meta.level == 1 && strcmp(func2str(plotFunc), 'graphDepthProfileGeneric') qcSet = str2double(readProperty('toolbox.qc_set')); diff --git a/Graph/lineCastVar.m b/Graph/lineCastVar.m index 7835f00c5..f209e25ac 100644 --- a/Graph/lineCastVar.m +++ b/Graph/lineCastVar.m @@ -162,7 +162,7 @@ function lineCastVar(sample_data, varNames, isQC, saveToFile, exportDir) set(hAxCastVar, 'YLim', [yMin, yMax]); hold(hAxCastVar, 'on'); - cMap = colormap(hAxCastVar, jet(lenSampleData)); + cMap = colormap(hAxCastVar, parula(lenSampleData)); cMap = flipud(cMap); end diff --git a/Graph/lineMooring1DVar.m b/Graph/lineMooring1DVar.m index f0e5a9c23..b74a0488f 100644 --- a/Graph/lineMooring1DVar.m +++ b/Graph/lineMooring1DVar.m @@ -18,32 +18,32 @@ function lineMooring1DVar(sample_data, varName, isQC, saveToFile, exportDir) % % -% Copyright (c) 2009, eMarine Information Infrastructure (eMII) and Integrated +% Copyright (c) 2009, eMarine Information Infrastructure (eMII) and Integrated % Marine Observing System (IMOS). % All rights reserved. -% -% Redistribution and use in source and binary forms, with or without +% +% Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: -% -% * Redistributions of source code must retain the above copyright notice, +% +% * Redistributions of source code must retain the above copyright notice, % this list of conditions and the following disclaimer. -% * Redistributions in binary form must reproduce the above copyright -% notice, this list of conditions and the following disclaimer in the +% * Redistributions in binary form must reproduce the above copyright +% notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. -% * Neither the name of the eMII/IMOS nor the names of its contributors -% may be used to endorse or promote products derived from this software +% * Neither the name of the eMII/IMOS nor the names of its contributors +% may be used to endorse or promote products derived from this software % without specific prior written permission. -% -% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. % narginchk(5,5); @@ -85,7 +85,7 @@ function lineMooring1DVar(sample_data, varName, isQC, saveToFile, exportDir) iTime = getVar(sample_data{i}.dimensions, 'TIME'); iVar = getVar(sample_data{i}.variables, varName); iGood = true(size(sample_data{i}.dimensions{iTime}.data)); - + % the variable exists, is QC'd and is 1D if isQC && iVar && size(sample_data{i}.variables{iVar}.data, 2) == 1 %get time and var QC information @@ -119,6 +119,8 @@ function lineMooring1DVar(sample_data, varName, isQC, saveToFile, exportDir) initiateFigure = true; isPlottable = false; +backgroundColor = [0.75 0.75 0.75]; + for i=1:lenSampleData % instrument description if ~isempty(strtrim(sample_data{iSort(i)}.instrument)) @@ -133,13 +135,13 @@ function lineMooring1DVar(sample_data, varName, isQC, saveToFile, exportDir) end instrumentDesc{i + 1} = [strrep(instrumentDesc{i + 1}, '_', ' ') ' (' num2str(metaDepth(i)) 'm' instrumentSN ')']; - + %look for time and relevant variable iTime = getVar(sample_data{iSort(i)}.dimensions, 'TIME'); iVar = getVar(sample_data{iSort(i)}.variables, varName); if iVar > 0 && size(sample_data{iSort(i)}.variables{iVar}.data, 2) == 1 && ... % we're only plotting 1D variables but no current - all(~strcmpi(sample_data{iSort(i)}.variables{iVar}.name, {'UCUR', 'VCUR', 'WCUR', 'CDIR', 'CSPD', 'VEL1', 'VEL2', 'VEL3'})) + all(~strcmpi(sample_data{iSort(i)}.variables{iVar}.name, {'UCUR', 'VCUR', 'WCUR', 'CDIR', 'CSPD', 'VEL1', 'VEL2', 'VEL3'})) if initiateFigure fileName = genIMOSFileName(sample_data{iSort(i)}, 'png'); visible = 'on'; @@ -159,16 +161,37 @@ function lineMooring1DVar(sample_data, varName, isQC, saveToFile, exportDir) set(hAxMooringVar, 'XLim', [xMin, xMax]); hold(hAxMooringVar, 'on'); - % reverse the colorbar as we want surface in red and bottom in blue - cMap = colormap(hAxMooringVar, jet(lenSampleData)); + % dummy entry for first entry in legend + hLineVar(1) = plot(0, 0, 'Color', backgroundColor, 'Visible', 'off'); % color grey same as background (invisible) + + % set data cursor mode custom display + dcm_obj = datacursormode(hFigMooringVar); + set(dcm_obj, 'UpdateFcn', {@customDcm, sample_data}); + + % set zoom datetick update + datetick(hAxMooringVar, 'x', 'dd-mm-yy HH:MM:SS', 'keepticks'); + zoomH = zoom(hFigMooringVar); + panH = pan(hFigMooringVar); + set(zoomH,'ActionPostCallback',{@zoomDateTick, hAxMooringVar}); + set(panH,'ActionPostCallback',{@zoomDateTick, hAxMooringVar}); + + try + defaultColormapFh = str2func(readProperty('visualQC.defaultColormap')); + cMap = colormap(hAxMooringVar, defaultColormapFh(lenSampleData)); + catch e + cMap = colormap(hAxMooringVar, parula(lenSampleData)); + end + % reverse the colorbar as we want surface instruments with warmer colors cMap = flipud(cMap); initiateFigure = false; end if strcmpi(varName, 'DEPTH') - hLineVar(1) = line([xMin, xMax], [metaDepth(i), metaDepth(i)], ... + hNominalDepth = line([xMin, xMax], [metaDepth(i), metaDepth(i)], ... 'Color', 'black'); + % turn off legend entry for this plot + set(get(get(hNominalDepth,'Annotation'),'LegendInformation'),'IconDisplayStyle','off'); end iGood = true(size(sample_data{iSort(i)}.variables{iVar}.data)); @@ -193,12 +216,16 @@ function lineMooring1DVar(sample_data, varName, isQC, saveToFile, exportDir) dataVar = sample_data{iSort(i)}.variables{iVar}.data; dataVar(~iGood) = NaN; - + hLineVar(i + 1) = line(xLine, ... dataVar, ... 'Color', cMap(i, :), ... 'LineStyle', lineStyle{mod(i, lenLineStyle)+1}); - + userData.idx = iSort(i); + userData.xName = 'TIME'; + userData.yName = varName; + set(hLineVar(i + 1), 'UserData', userData); + clear('userData'); % Let's redefine properties after pcolor to make sure grid lines appear % above color data and XTick and XTickLabel haven't changed set(hAxMooringVar, ... @@ -208,7 +235,7 @@ function lineMooring1DVar(sample_data, varName, isQC, saveToFile, exportDir) 'Layer', 'top'); % set background to be grey - set(hAxMooringVar, 'Color', [0.75 0.75 0.75]) + set(hAxMooringVar, 'Color', backgroundColor) end end end @@ -230,45 +257,35 @@ function lineMooring1DVar(sample_data, varName, isQC, saveToFile, exportDir) datetick(hAxMooringVar, 'x', 'dd-mm-yy HH:MM:SS', 'keepticks'); - % we try to split the legend in two location horizontally - nLine = length(hLineVar); - if nLine > 2 - nLine1 = ceil(nLine/2); - - hLegend(1) = multipleLegend(hAxMooringVar, ... - hLineVar(1:nLine1), instrumentDesc(1:nLine1), ... - 'Interpreter', 'none', ... - 'Location', 'SouthOutside'); - hLegend(2) = multipleLegend(hAxMooringVar, ... - hLineVar(nLine1+1:nLine), instrumentDesc(nLine1+1:nLine), ... - 'Interpreter', 'none', ... - 'Location', 'SouthOutside'); - - posAx = get(hAxMooringVar, 'Position'); - - pos1 = get(hLegend(1), 'Position'); - pos2 = get(hLegend(2), 'Position'); - maxWidth = max(pos1(3), pos2(3)); - - set(hLegend(1), 'Position', [posAx(1), pos1(2), pos1(3), pos1(4)]); - set(hLegend(2), 'Position', [posAx(3) - maxWidth/2, pos1(2), pos2(3), pos2(4)]); - - % set position on legends above modifies position of axis so we - % re-initialise it - set(hAxMooringVar, 'Position', posAx); + % we try to split the legend, maximum 3 columns + fontSizeAx = get(hAxMooringVar,'FontSize'); + fontSizeLb = get(get(hAxMooringVar,'XLabel'),'FontSize'); + xscale = 0.9; + if numel(instrumentDesc) < 4 + nCols = 1; + elseif numel(instrumentDesc) < 8 + nCols = 2; else - hLegend = legend(hAxMooringVar, ... - hLineVar, instrumentDesc, ... - 'Interpreter', 'none', ... - 'Location', 'SouthOutside'); - - % unfortunately we need to do this hack so that we have consistency with - % the case above - posAx = get(hAxMooringVar, 'Position'); - set(hAxMooringVar, 'Position', posAx); + nCols = 3; + fontSizeAx = fontSizeAx - 1; + xscale = 0.75; + end + hYBuffer = 1.1 * (2*(fontSizeAx + fontSizeLb)); + hLegend = legendflex(hAxMooringVar, instrumentDesc,... + 'anchor', [6 2], ... + 'buffer', [0 -hYBuffer], ... + 'ncol', nCols,... + 'FontSize', fontSizeAx,... + 'xscale',xscale); + posAx = get(hAxMooringVar, 'Position'); + set(hLegend, 'Units', 'Normalized', 'color', backgroundColor); + posLh = get(hLegend, 'Position'); + if posLh(2) < 0 + set(hLegend, 'Position',[posLh(1), abs(posLh(2)), posLh(3), posLh(4)]); + set(hAxMooringVar, 'Position',[posAx(1), posAx(2)+2*abs(posLh(2)), posAx(3), posAx(4)-2*abs(posLh(2))]); + else + set(hAxMooringVar, 'Position',[posAx(1), posAx(2)+abs(posLh(2)), posAx(3), posAx(4)-abs(posLh(2))]); end - -% set(hLegend, 'Box', 'off', 'Color', 'none'); if saveToFile % ensure the printed version is the same whatever the screen used. @@ -290,4 +307,67 @@ function lineMooring1DVar(sample_data, varName, isQC, saveToFile, exportDir) end end +%% + function datacursorText = customDcm(~, event_obj, sample_data) + %customDatacursorText : custom data tip display + + % Display the position of the data cursor + % obj Currently not used (empty) + % event_obj Handle to event object + % output_txt Data cursor text string (string or cell array of strings). + % event_obj + % xVarName, yVarName, zVarName : x, y, z (coloured by variable) names, + + dataIndex = get(event_obj,'DataIndex'); + posClic = get(event_obj,'Position'); + + p=get(event_obj,'Target'); + userData = get(p, 'UserData'); + + xName = userData.xName; + yName = userData.yName; + + sam = sample_data{userData.idx}; + + ixVar = getVar(sam.dimensions, xName); + if ixVar ~= 0 + xUnits = sam.dimensions{ixVar}.units; + else + % generalized case pass in a variable instead of a dimension + ixVar = getVar(sam.variables, xName); + xUnits = sam.variables{ixVar}.units; + end + + iyVar = getVar(sam.dimensions, yName); + if iyVar ~= 0 + yUnits = sam.dimensions{iyVar}.units; + else + % generalized case pass in a variable instead of a dimension + iyVar = getVar(sam.variables, yName); + yUnits = sam.variables{iyVar}.units; + end + + if strcmp(xName, 'TIME') + xStr = datestr(posClic(1),'yyyy-mm-dd HH:MM:SS.FFF'); + else + xStr = [num2str(posClic(1)) ' ' xUnits]; + end + + if strcmp(yName, 'TIME') + yStr = datestr(posClic(2),'yyyy-mm-dd HH:MM:SS.FFF'); + else + yStr = [num2str(posClic(2)) ' (' yUnits ')']; %num2str(posClic(2),4) + end + + datacursorText = {get(p,'DisplayName'),... + [xName ': ' xStr],... + [yName ': ' yStr]}; + %datacursorText{end+1} = ['FileName: ',get(p,'Tag')]; + end + +%% + function zoomDateTick(obj,event_obj,hAx) + datetick(hAx,'x','dd-mm-yy HH:MM:SS','keeplimits') + end + end \ No newline at end of file diff --git a/Graph/lineMooring2DVarSection.m b/Graph/lineMooring2DVarSection.m index 6c7107a1e..8789a3777 100644 --- a/Graph/lineMooring2DVarSection.m +++ b/Graph/lineMooring2DVarSection.m @@ -115,6 +115,8 @@ function lineMooring2DVarSection(sample_data, varName, timeValue, isQC, saveToFi dimTitle = imosParameters(dimName, 'long_name'); dimUnit = imosParameters(dimName, 'uom'); +backgroundColor = [0.75 0.75 0.75]; + if iVar > 0 if initiateFigure fileName = genIMOSFileName(sample_data, 'png'); @@ -136,6 +138,9 @@ function lineMooring2DVarSection(sample_data, varName, timeValue, isQC, saveToFi hold(hAxCastVar, 'on'); + % dummy entry for first entry in legend + hLineVar(1) = plot(0, 0, 'o', 'color', backgroundColor, 'Visible', 'off'); % color grey same as background (invisible) + yLine = sample_data.dimensions{i2Ddim}.data; dataVar = sample_data.variables{iVar}.data(iX); @@ -242,7 +247,7 @@ function lineMooring2DVarSection(sample_data, varName, timeValue, isQC, saveToFi 'Layer', 'top'); % set background to be grey - set(hAxCastVar, 'Color', [0.75 0.75 0.75]) + set(hAxCastVar, 'Color', backgroundColor) end if ~initiateFigure @@ -254,12 +259,12 @@ function lineMooring2DVarSection(sample_data, varName, timeValue, isQC, saveToFi hLineVar = [hLineVar; hLineFlag]; instrumentDesc = [instrumentDesc; flagDesc]; - + % Matlab >R2015 legend entries for data which are not plotted + % will be shown with reduced opacity hLegend = legend(hAxCastVar, ... - hLineVar, instrumentDesc, ... + hLineVar, regexprep(instrumentDesc,'_','\_'), ... 'Interpreter', 'none', ... 'Location', 'SouthOutside'); - % set(hLegend, 'Box', 'off', 'Color', 'none'); end diff --git a/Graph/pcolorMooring2DVar.m b/Graph/pcolorMooring2DVar.m index b776efef4..bb4c50d53 100644 --- a/Graph/pcolorMooring2DVar.m +++ b/Graph/pcolorMooring2DVar.m @@ -116,10 +116,10 @@ function pcolorMooring2DVar(sample_data, varName, isQC, saveToFile, exportDir) cMap = 'rkbwr'; cType = 'direction'; case {'CSPD'} % [0; oo[ paremeters - cMap = 'jet'; + cMap = 'parula'; cType = 'positiveFromZero'; otherwise - cMap = 'jet'; + cMap = 'parula'; cType = ''; end diff --git a/Graph/scatterMooring1DVarAgainstDepth.m b/Graph/scatterMooring1DVarAgainstDepth.m index ede728a66..89f936ed1 100644 --- a/Graph/scatterMooring1DVarAgainstDepth.m +++ b/Graph/scatterMooring1DVarAgainstDepth.m @@ -18,32 +18,32 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, % % -% Copyright (c) 2009, eMarine Information Infrastructure (eMII) and Integrated +% Copyright (c) 2009, eMarine Information Infrastructure (eMII) and Integrated % Marine Observing System (IMOS). % All rights reserved. -% -% Redistribution and use in source and binary forms, with or without +% +% Redistribution and use in source and binary forms, with or without % modification, are permitted provided that the following conditions are met: -% -% * Redistributions of source code must retain the above copyright notice, +% +% * Redistributions of source code must retain the above copyright notice, % this list of conditions and the following disclaimer. -% * Redistributions in binary form must reproduce the above copyright -% notice, this list of conditions and the following disclaimer in the +% * Redistributions in binary form must reproduce the above copyright +% notice, this list of conditions and the following disclaimer in the % documentation and/or other materials provided with the distribution. -% * Neither the name of the eMII/IMOS nor the names of its contributors -% may be used to endorse or promote products derived from this software +% * Neither the name of the eMII/IMOS nor the names of its contributors +% may be used to endorse or promote products derived from this software % without specific prior written permission. -% -% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. % narginchk(5,5); @@ -67,6 +67,7 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, xResolution = monitorRec(:, 3)-monitorRec(:, 1); iBigMonitor = xResolution == max(xResolution); if sum(iBigMonitor)==2, iBigMonitor(2) = false; end % in case exactly same monitors + title = [sample_data{1}.deployment_code ' mooring''s instruments ' stringQC '''d good ' varTitle]; %sort instruments by depth @@ -83,19 +84,19 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, metaDepth(i) = NaN; end iTime = getVar(sample_data{i}.dimensions, 'TIME'); - iVar = getVar(sample_data{i}.variables, varName); + izVar = getVar(sample_data{i}.variables, varName); iGood = true(size(sample_data{i}.dimensions{iTime}.data)); - + % the variable exists, is QC'd and is 1D - if isQC && iVar && size(sample_data{i}.variables{iVar}.data, 2) == 1 + if isQC && izVar && size(sample_data{i}.variables{izVar}.data, 2) == 1 %get time and var QC information timeFlags = sample_data{i}.dimensions{iTime}.flags; - varFlags = sample_data{i}.variables{iVar}.flags; + varFlags = sample_data{i}.variables{izVar}.flags; iGood = (timeFlags == 1 | timeFlags == 2) & (varFlags == 1 | varFlags == 2); end - if iVar + if izVar if all(~iGood) continue; end @@ -125,29 +126,31 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, %look for time and relevant variable iTime = getVar(sample_data{iSort(i)}.dimensions, 'TIME'); iDepth = getVar(sample_data{iSort(i)}.variables, 'DEPTH'); - iVar = getVar(sample_data{iSort(i)}.variables, varName); + izVar = getVar(sample_data{iSort(i)}.variables, varName); - if iVar > 0 && iDepth > 0 && ... - size(sample_data{iSort(i)}.variables{iVar}.data, 2) == 1 && ... % we're only plotting 1D variables with depth variable but no current - all(~strcmpi(sample_data{iSort(i)}.variables{iVar}.name, {'UCUR', 'VCUR', 'WCUR', 'CDIR', 'CSPD', 'VEL1', 'VEL2', 'VEL3'})) - iGood = true(size(sample_data{iSort(i)}.variables{iVar}.data)); + if izVar > 0 && iDepth > 0 && ... + size(sample_data{iSort(i)}.variables{izVar}.data, 2) == 1 && ... % we're only plotting 1D variables with depth variable but no current + all(~strcmpi(sample_data{iSort(i)}.variables{izVar}.name, {'UCUR', 'VCUR', 'WCUR', 'CDIR', 'CSPD', 'VEL1', 'VEL2', 'VEL3'})) + iGood = true(size(sample_data{iSort(i)}.variables{izVar}.data)); if isQC %get time, depth and var QC information timeFlags = sample_data{iSort(i)}.dimensions{iTime}.flags; depthFlags = sample_data{iSort(i)}.variables{iDepth}.flags; - varFlags = sample_data{iSort(i)}.variables{iVar}.flags; + varFlags = sample_data{iSort(i)}.variables{izVar}.flags; iGood = (timeFlags == 1 | timeFlags == 2) & (varFlags == 1 | varFlags == 2) & (depthFlags == 1 | depthFlags == 2); end if any(iGood) isPlottable(i) = true; - minClim = min(minClim, min(sample_data{iSort(i)}.variables{iVar}.data(iGood))); - maxClim = max(maxClim, max(sample_data{iSort(i)}.variables{iVar}.data(iGood))); + minClim = min(minClim, min(sample_data{iSort(i)}.variables{izVar}.data(iGood))); + maxClim = max(maxClim, max(sample_data{iSort(i)}.variables{izVar}.data(iGood))); end end end +backgroundColor = [0.75 0.75 0.75]; + if any(isPlottable) % collect visualQC config try @@ -155,7 +158,7 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, catch e %#ok fastScatter = true; end - + initiateFigure = true; for i=1:lenSampleData % instrument description @@ -175,7 +178,7 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, %look for time and relevant variable iTime = getVar(sample_data{iSort(i)}.dimensions, 'TIME'); iDepth = getVar(sample_data{iSort(i)}.variables, 'DEPTH'); - iVar = getVar(sample_data{iSort(i)}.variables, varName); + izVar = getVar(sample_data{iSort(i)}.variables, varName); if isPlottable(i) if initiateFigure @@ -197,21 +200,44 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, set(hAxMooringVar, 'XLim', [xMin, xMax]); hold(hAxMooringVar, 'on'); + % dummy entry for first entry in legend + hScatterVar(1) = plot(0, 0, 'Color', backgroundColor, 'Visible', 'off'); % color grey same as background (invisible) + + % set data cursor mode custom display + dcm_obj = datacursormode(hFigMooringVar); + set(dcm_obj, 'UpdateFcn', {@customDcm, sample_data}, 'SnapToDataVertex','on'); + + % set zoom datetick update + datetick(hAxMooringVar, 'x', 'dd-mm-yy HH:MM:SS', 'keepticks'); + zoomH = zoom(hFigMooringVar); + panH = pan(hFigMooringVar); + set(zoomH,'ActionPostCallback',{@zoomDateTick, hAxMooringVar}); + set(panH,'ActionPostCallback',{@zoomDateTick, hAxMooringVar}); + + try + nColors = str2double(readProperty('visualQC.ncolors')); + defaultColormapFh = str2func(readProperty('visualQC.defaultColormap')); + cMap = colormap(hAxMooringVar, defaultColormapFh(nColors)); + catch e + nColors = 64; + cMap = colormap(hAxMooringVar, parula(nColors)); + end + hCBar = colorbar('peer', hAxMooringVar); set(get(hCBar, 'Title'), 'String', [varName ' (' varUnit ')'], 'Interpreter', 'none'); initiateFigure = false; end - iGood = true(size(sample_data{iSort(i)}.variables{iVar}.data)); + iGood = true(size(sample_data{iSort(i)}.variables{izVar}.data)); iGoodDepth = iGood; if isQC %get time, depth and var QC information timeFlags = sample_data{iSort(i)}.dimensions{iTime}.flags; depthFlags = sample_data{iSort(i)}.variables{iDepth}.flags; - varFlags = sample_data{iSort(i)}.variables{iVar}.flags; - varValues = sample_data{iSort(i)}.variables{iVar}.data; + varFlags = sample_data{iSort(i)}.variables{izVar}.flags; + varValues = sample_data{iSort(i)}.variables{izVar}.data; iGood = (timeFlags == 1 | timeFlags == 2) & ... (varFlags == 1 | varFlags == 2) & ... @@ -228,6 +254,13 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, depth(~iGoodDepth) = metaDepth(i); depth = depth(iGood); + % data for customDcm + userData.idx = iSort(i); + userData.xName = 'TIME'; + userData.yName = 'DEPTH'; + userData.zName = varName; + userData.iGood = iGood; + if fastScatter % for performance, we use plot (1 single handle object % returned) rather than scatter (as many handles returned as @@ -240,21 +273,26 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, % first to last) of the total points given. We choose an ordering from % centre to both ends of colorbar in order to keep extreme colors visible % though. + h = plotclr(hAxMooringVar, ... sample_data{iSort(i)}.dimensions{iTime}.data(iGood), ... depth, ... - sample_data{iSort(i)}.variables{iVar}.data(iGood), ... + sample_data{iSort(i)}.variables{izVar}.data(iGood), ... markerStyle{mod(i, lenMarkerStyle)+1}, ... - [minClim maxClim]); + [minClim maxClim], 'DisplayName', instrumentDesc{i+1},'UserData', userData); else - h = scatter(hAxMooringVar, ... - sample_data{iSort(i)}.dimensions{iTime}.data(iGood), ... - depth, ... - 5, ... - sample_data{iSort(i)}.variables{iVar}.data(iGood), ... - markerStyle{mod(i, lenMarkerStyle)+1}, ... - MarkerFaceColor, 'none'); + % faster than scatter, but legend requires adjusting + h = fastScatterMesh( hAxMooringVar,... + sample_data{iSort(i)}.dimensions{iTime}.data(iGood),... + depth,... + sample_data{iSort(i)}.variables{izVar}.data(iGood),... + [minClim maxClim],... + 'Marker',markerStyle{mod(i, lenMarkerStyle)+1},... + 'MarkerSize',2.5,... + 'DisplayName',[markerStyle{mod(i, lenMarkerStyle)+1} ' ' instrumentDesc{i+1}],... + 'UserData', userData); end + clear('userData'); if ~isempty(h), hScatterVar(i + 1) = h; end @@ -267,12 +305,19 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, 'Layer', 'top'); % set background to be grey - set(hAxMooringVar, 'Color', [0.75 0.75 0.75]) + set(hAxMooringVar, 'Color', backgroundColor) end % we plot the instrument nominal depth - hScatterVar(1) = line([xMin, xMax], [metaDepth(i), metaDepth(i)], ... + hNominalDepth = line([xMin, xMax], [metaDepth(i), metaDepth(i)], ... 'Color', 'black'); + % turn off legend entry for this plot + set(get(get(hNominalDepth,'Annotation'),'LegendInformation'),'IconDisplayStyle','off'); + % with 'HitTest' == 'off' plot should not be selectable but + % just in case set idx = NaN for customDcm + userData.idx = NaN; + set(hNominalDepth, 'UserData', userData, 'HitTest', 'off'); + clear('userData'); end end else @@ -294,30 +339,48 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, % we try to split the legend in two location horizontally nLine = length(hScatterVar); + fontSizeAx = get(hAxMooringVar,'FontSize'); + fontSizeLb = get(get(hAxMooringVar,'XLabel'),'FontSize'); + xscale = 0.9; if nLine > 2 - nLine1 = ceil(nLine/2); - - hLegend(1) = multipleLegend(hAxMooringVar, ... - hScatterVar(1:nLine1), instrumentDesc(1:nLine1), ... - 'Interpreter', 'none', ... - 'Location', 'SouthOutside'); - hLegend(2) = multipleLegend(hAxMooringVar, ... - hScatterVar(nLine1+1:nLine), instrumentDesc(nLine1+1:nLine), ... - 'Interpreter', 'none', ... - 'Location', 'SouthOutside'); - + if numel(instrumentDesc) < 4 + nCols = 1; + elseif numel(instrumentDesc) < 8 + nCols = 2; + else + nCols = 3; + fontSizeAx = fontSizeAx - 1; + xscale = 0.75; + end + hYBuffer = 1.1 * (2*(fontSizeAx + fontSizeLb)); + hLegend = legendflex(hAxMooringVar, instrumentDesc, ... + 'anchor', [6 2], ... + 'buffer', [0 -hYBuffer], ... + 'ncol', nCols,... + 'FontSize', fontSizeAx, ... + 'xscale', xscale); + entries = get(hLegend,'children'); + % if used mesh for scatter plot then have to clean up legend + % entries + for ii = 1:numel(entries) + if strcmpi(get(entries(ii),'Type'),'patch') + XData = get(entries(ii),'XData'); + YData = get(entries(ii),'YData'); + %CData = get(entries(ii),'CData'); + set(entries(ii),'XData',repmat(mean(XData),size(XData))) + set(entries(ii),'YData',repmat(mean(YData),size(XData))) + %set(entries(ii),'CData',CData(1)) + end + end posAx = get(hAxMooringVar, 'Position'); - - pos1 = get(hLegend(1), 'Position'); - pos2 = get(hLegend(2), 'Position'); - maxWidth = max(pos1(3), pos2(3)); - - set(hLegend(1), 'Position', [posAx(1), pos1(2), pos1(3), pos1(4)]); - set(hLegend(2), 'Position', [posAx(3) - maxWidth/2, pos1(2), pos2(3), pos2(4)]); - - % set position on legends above modifies position of axis so we - % re-initialise it - set(hAxMooringVar, 'Position', posAx); + set(hLegend, 'Units', 'Normalized', 'color', backgroundColor); + posLh = get(hLegend, 'Position'); + if posLh(2) < 0 + set(hLegend, 'Position',[posLh(1), abs(posLh(2)), posLh(3), posLh(4)]); + set(hAxMooringVar, 'Position',[posAx(1), posAx(2)+2*abs(posLh(2)), posAx(3), posAx(4)-2*abs(posLh(2))]); + else + set(hAxMooringVar, 'Position',[posAx(1), posAx(2)+abs(posLh(2)), posAx(3), posAx(4)-abs(posLh(2))]); + end else % doesn't make sense to continue and export to file since seing a % scatter plot in depth only helps to analyse the data in its @@ -326,7 +389,7 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, return; end -% set(hLegend, 'Box', 'off', 'Color', 'none'); + % set(hLegend, 'Box', 'off', 'Color', 'none'); if saveToFile % ensure the printed version is the same whatever the screen used. @@ -335,7 +398,7 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, % preserve the color scheme set(hFigMooringVar, 'InvertHardcopy', 'off'); - + fileName = strrep(fileName, '_PARAM_', ['_', varName, '_']); % IMOS_[sub-facility_code]_[site_code]_FV01_[deployment_code]_[PLOT-TYPE]_[PARAM]_C-[creation_date].png fileName = strrep(fileName, '_PLOT-TYPE_', '_SCATTER_'); @@ -348,4 +411,119 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, end end + function datacursorText = customDcm(~, event_obj, sample_data) + %customDcm : custom data tip display for 1D Var Against Depth plot + % + % Display the position of the data cursor + % obj Currently not used (empty) + % event_obj Handle to event object + % datacursorText Data cursor text string (string or cell array of strings). + % sample_data : the data plotted, since only good data is plotted require + % iGood passed in on UserData + % + % NOTES + % - the multiple try catch blocks are there to trap and modifications of + % the UserData field (by say an external function called before entry into + % customDcm + + dataIndex = get(event_obj,'DataIndex'); + posClic = get(event_obj,'Position'); + + target_obj=get(event_obj,'Target'); + userData = get(target_obj, 'UserData'); + + % somehow selected nominal depth line plot + if isnan(userData.idx), return; end + + sam = sample_data{userData.idx}; + + xName = userData.xName; + yName = userData.yName; + zName = userData.zName; + + try + dStr = get(target_obj,'DisplayName'); + catch + dStr = 'UNKNOWN'; + end + + try + % generalized case pass in a variable instead of a dimension + ixVar = getVar(sam.dimensions, xName); + if ixVar ~= 0 + xUnits = sam.dimensions{ixVar}.units; + else + ixVar = getVar(sam.variables, xName); + xUnits = sam.variables{ixVar}.units; + end + if strcmp(xName, 'TIME') + xStr = datestr(posClic(1),'yyyy-mm-dd HH:MM:SS.FFF'); + else + xStr = [num2str(posClic(1)) ' ' xUnits]; + end + catch + xStr = 'NO DATA'; + end + + try + % generalized case pass in a variable instead of a dimension + iyVar = getVar(sam.dimensions, yName); + if iyVar ~= 0 + yUnits = sam.dimensions{iyVar}.units; + else + iyVar = getVar(sam.variables, yName); + yUnits = sam.variables{iyVar}.units; + end + if strcmp(yName, 'TIME') + yStr = datestr(posClic(2),'yyyy-mm-dd HH:MM:SS.FFF'); + else + yStr = [num2str(posClic(2)) ' ' yUnits]; %num2str(posClic(2),4) + end + catch + yStr = 'NO DATA'; + end + + try + % generalized case pass in a variable instead of a dimension + izVar = getVar(sam.dimensions, zName); + if izVar ~= 0 + zUnits = sam.dimensions{izVar}.units; + zData = sam.dimensions{izVar}.data(userData.iGood); + else + izVar = getVar(sam.variables, zName); + zUnits = sam.variables{izVar}.units; + zData = sam.variables{izVar}.data(userData.iGood); + end + iTime = getVar(sam.dimensions, 'TIME'); + timeData = sam.dimensions{iTime}.data(userData.iGood); + idx = find(abs(timeData-posClic(1))', '<', 'p', 'h'}; lenMarkerStyle = length(markerStyle); @@ -178,6 +186,8 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, end end +backgroundColor = [0.75 0.75 0.75]; + if any(isPlottable) % collect visualQC config try @@ -187,8 +197,8 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, end % define cMap, cLim and cType per parameter - switch varName - case {'UCUR', 'VCUR', 'WCUR', 'VEL1', 'VEL2', 'VEL3'} % 0 centred parameters + switch varName(1:4) + case {'UCUR', 'VCUR', 'WCUR', 'ECUR', 'VEL1', 'VEL2', 'VEL3'} % 0 centred parameters cMap = 'r_b'; cType = 'centeredOnZero'; CLim = [-yLimMax yLimMax]; @@ -197,11 +207,25 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, cType = 'direction'; CLim = [0 360]; case {'CSPD'} % [0; oo[ paremeters - cMap = 'jet'; + try + nColors = str2double(readProperty('visualQC.ncolors')); + defaultColormapFh = str2func(readProperty('visualQC.defaultColormap')); + cMap = defaultColormapFh(nColors); + catch e + nColors = 64; + cMap = parula(nColors); + end cType = 'positiveFromZero'; CLim = [0 yLimMax]; otherwise - cMap = 'jet'; + try + nColors = str2double(readProperty('visualQC.ncolors')); + defaultColormapFh = str2func(readProperty('visualQC.defaultColormap')); + cMap = defaultColormapFh(nColors); + catch e + nColors = 64; + cMap = parula(nColors); + end cType = ''; CLim = [yLimMin yLimMax]; end @@ -249,6 +273,20 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, set(hAxMooringVar, 'XLim', [xMin, xMax]); hold(hAxMooringVar, 'on'); + % dummy entry for first entry in legend + hScatterVar(1) = plot(0, 0, 'Color', backgroundColor, 'Visible', 'off'); % color grey same as background (invisible) + + % set data cursor mode custom display + dcm_obj = datacursormode(hFigMooringVar); + set(dcm_obj, 'UpdateFcn', {@customDcm, sample_data}, 'SnapToDataVertex','on'); + + % set zoom datetick update + datetick(hAxMooringVar, 'x', 'dd-mm-yy HH:MM:SS', 'keepticks'); + zoomH = zoom(hFigMooringVar); + panH = pan(hFigMooringVar); + set(zoomH,'ActionPostCallback',{@zoomDateTick, hAxMooringVar}); + set(panH,'ActionPostCallback',{@zoomDateTick, hAxMooringVar}); + hCBar = colorbar('peer', hAxMooringVar, 'YLim', CLim); colormap(hAxMooringVar, cMap); set(hAxMooringVar, 'CLim', CLim); @@ -282,8 +320,8 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, iGoodHeight = any(iGood, 1); nGoodHeight = sum(iGoodHeight); -% nGoodHeight = nGoodHeight + 1; -% iGoodHeight(nGoodHeight) = 1; + % nGoodHeight = nGoodHeight + 1; + % iGoodHeight(nGoodHeight) = 1; if all(all(~iGood)) && isQC fprintf('%s\n', ['Warning : in ' sample_data{iSort(i)}.toolbox_input_file ... @@ -307,6 +345,13 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, dataVar(~iGood) = NaN; for j=1:nGoodHeight + % data for customDcm + userData.idx = iSort(i); + userData.jHeight = j; + userData.xName = 'TIME'; + userData.yName = 'DEPTH'; + userData.zName = varName; + if fastScatter % for performance, we use plot (1 single handle object % returned) rather than scatter (as many handles returned as @@ -324,16 +369,22 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, dataDepth - yScatter(j), ... dataVar(:, j), ... markerStyle{mod(i, lenMarkerStyle)+1}, ... - CLim); + CLim,... + 'DisplayName',instrumentDesc{i+1},... + 'UserData', userData); else - h = scatter(hAxMooringVar, ... - xScatter, ... - dataDepth - yScatter(j), ... - 5, ... - dataVar(:, j), ... - markerStyle{mod(i, lenMarkerStyle)+1}, ... - MarkerFaceColor, 'none'); + % faster than scatter, but legend requires adjusting + h = fastScatterMesh( hAxMooringVar,... + xScatter,... + dataDepth - yScatter(j),... + dataVar(:, j),... + CLim,... + 'Marker',markerStyle{mod(i, lenMarkerStyle)+1},... + 'MarkerSize',2.5,... + 'DisplayName',instrumentDesc{i+1},... + 'UserData', userData); end + clear('userData'); if ~isempty(h), hScatterVar(i + 1) = h; end end @@ -347,12 +398,19 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, 'Layer', 'top'); % set background to be grey - set(hAxMooringVar, 'Color', [0.75 0.75 0.75]) + set(hAxMooringVar, 'Color', backgroundColor) end % we plot the instrument nominal depth - hScatterVar(1) = line([xMin, xMax], [metaDepth(i), metaDepth(i)], ... + hNominalDepth = line([xMin, xMax], [metaDepth(i), metaDepth(i)], ... 'Color', 'black'); + % turn off legend entry for this plot + set(get(get(hNominalDepth,'Annotation'),'LegendInformation'),'IconDisplayStyle','off'); + % with 'HitTest' == 'off' plot should not be selectable but + % just in case set idx = NaN for customDcm + userData.idx = NaN; + set(hNominalDepth, 'UserData', userData, 'HitTest', 'off'); + clear('userData'); end end else @@ -368,45 +426,50 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, datetick(hAxMooringVar, 'x', 'dd-mm-yy HH:MM:SS', 'keepticks'); - % we try to split the legend in two location horizontally - nLine = length(hScatterVar); - if nLine > 2 - nLine1 = ceil(nLine/2); - - hLegend(1) = multipleLegend(hAxMooringVar, ... - hScatterVar(1:nLine1), instrumentDesc(1:nLine1), ... - 'Interpreter', 'none', ... - 'Location', 'SouthOutside'); - hLegend(2) = multipleLegend(hAxMooringVar, ... - hScatterVar(nLine1+1:nLine), instrumentDesc(nLine1+1:nLine), ... - 'Interpreter', 'none', ... - 'Location', 'SouthOutside'); - - posAx = get(hAxMooringVar, 'Position'); - - pos1 = get(hLegend(1), 'Position'); - pos2 = get(hLegend(2), 'Position'); - maxWidth = max(pos1(3), pos2(3)); - - set(hLegend(1), 'Position', [posAx(1), pos1(2), pos1(3), pos1(4)]); - set(hLegend(2), 'Position', [posAx(3) - maxWidth/2, pos1(2), pos2(3), pos2(4)]); - - % set position on legends above modifies position of axis so we - % re-initialise it - set(hAxMooringVar, 'Position', posAx); + % we try to split the legend horizontally, max 3 columns + fontSizeAx = get(hAxMooringVar,'FontSize'); + fontSizeLb = get(get(hAxMooringVar,'XLabel'),'FontSize'); + xscale = 0.9; + if numel(instrumentDesc) < 4 + nCols = 1; + elseif numel(instrumentDesc) < 8 + nCols = 2; else - hLegend = legend(hAxMooringVar, ... - hScatterVar, instrumentDesc, ... - 'Interpreter', 'none', ... - 'Location', 'SouthOutside'); - - % unfortunately we need to do this hack so that we have consistency with - % the case above - posAx = get(hAxMooringVar, 'Position'); - set(hAxMooringVar, 'Position', posAx); + nCols = 3; + fontSizeAx = fontSizeAx - 1; + xscale = 0.75; + end + hYBuffer = 1.1 * (2*(fontSizeAx + fontSizeLb)); + hLegend = legendflex(hAxMooringVar,instrumentDesc,... + 'anchor', [6 2], ... + 'buffer', [0 -hYBuffer], ... + 'ncol', nCols,... + 'FontSize', fontSizeAx,'xscale',xscale); + + % if used mesh for scatter plot then have to clean up legend + % entries + entries = get(hLegend,'children'); + for ii = 1:numel(entries) + if strcmpi(get(entries(ii),'Type'),'patch') + XData = get(entries(ii),'XData'); + YData = get(entries(ii),'YData'); + %CData = get(entries(ii),'CData'); + set(entries(ii),'XData',repmat(mean(XData),size(XData))) + set(entries(ii),'YData',repmat(mean(YData),size(XData))) + %set(entries(ii),'CData',CData(1)) + end + end + posAx = get(hAxMooringVar, 'Position'); + set(hLegend, 'Units', 'Normalized', 'color', backgroundColor) + posLh = get(hLegend, 'Position'); + if posLh(2) < 0 + set(hLegend, 'Position',[posLh(1), abs(posLh(2)), posLh(3), posLh(4)]); + set(hAxMooringVar, 'Position',[posAx(1), posAx(2)+2*abs(posLh(2)), posAx(3), posAx(4)-2*abs(posLh(2))]); + else + set(hAxMooringVar, 'Position',[posAx(1), posAx(2)+abs(posLh(2)), posAx(3), posAx(4)-abs(posLh(2))]); end -% set(hLegend, 'Box', 'off', 'Color', 'none'); + % set(hLegend, 'Box', 'off', 'Color', 'none'); if saveToFile % ensure the printed version is the same whatever the screen used. @@ -415,7 +478,7 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, % preserve the color scheme set(hFigMooringVar, 'InvertHardcopy', 'off'); - + fileName = strrep(fileName, '_PARAM_', ['_', varName, '_']); % IMOS_[sub-facility_code]_[site_code]_FV01_[deployment_code]_[PLOT-TYPE]_[PARAM]_C-[creation_date].png fileName = strrep(fileName, '_PLOT-TYPE_', '_SCATTER_'); @@ -428,4 +491,122 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, end end -end \ No newline at end of file +%% + function datacursorText = customDcm(~, event_obj, sample_data) + %customDcm : custom data tip display for 1D Var Against Depth plot + % + % Display the position of the data cursor + % obj Currently not used (empty) + % event_obj Handle to event object + % datacursorText Data cursor text string (string or cell array of strings). + % sample_data : the data plotted, since only good data is plotted require + % iGood passed in on UserData + % + % NOTES + % - the multiple try catch blocks are there to trap and modifications of + % the UserData field (by say an external function called before entry into + % customDcm + + dataIndex = get(event_obj,'DataIndex'); + posClic = get(event_obj,'Position'); + + target_obj=get(event_obj,'Target'); + userData = get(target_obj, 'UserData'); + + % somehow selected nominal depth line plot + if isnan(userData.idx), return; end + + sam = sample_data{userData.idx}; + + xName = userData.xName; + yName = userData.yName; + zName = userData.zName; + + try + dStr = get(target_obj,'DisplayName'); + catch + dStr = 'UNKNOWN'; + end + + try + % generalized case pass in a variable instead of a dimension + ixVar = getVar(sam.dimensions, xName); + if ixVar ~= 0 + xUnits = sam.dimensions{ixVar}.units; + else + ixVar = getVar(sam.variables, xName); + xUnits = sam.variables{ixVar}.units; + end + if strcmp(xName, 'TIME') + xStr = datestr(posClic(1),'yyyy-mm-dd HH:MM:SS.FFF'); + else + xStr = [num2str(posClic(1)) ' ' xUnits]; + end + catch + xStr = 'NO DATA'; + end + + try + % generalized case pass in a variable instead of a dimension + iyVar = getVar(sam.dimensions, yName); + if iyVar ~= 0 + yUnits = sam.dimensions{iyVar}.units; + else + iyVar = getVar(sam.variables, yName); + yUnits = sam.variables{iyVar}.units; + end + if strcmp(yName, 'TIME') + yStr = datestr(posClic(2),'yyyy-mm-dd HH:MM:SS.FFF'); + else + yStr = [num2str(posClic(2)) ' ' yUnits]; %num2str(posClic(2),4) + end + catch + yStr = 'NO DATA'; + end + + try + % generalized case pass in a variable instead of a dimension + izVar = getVar(sam.dimensions, zName); + if izVar ~= 0 + zUnits = sam.dimensions{izVar}.units; + zData = sam.dimensions{izVar}.data; + else + izVar = getVar(sam.variables, zName); + zUnits = sam.variables{izVar}.units; + zData = sam.variables{izVar}.data; + end + + iTime = getVar(sam.dimensions, 'TIME'); + timeData = sam.dimensions{iTime}.data; + idx = find(abs(timeData-posClic(1)) conversion dBar to MPa (see rinko correction formula pdf). DO is in % of dissolved oxygen during calibration at this stage. if psalIdx > 0 psal = sam.variables{psalIdx}.data; diff --git a/Preprocessing/timeDriftPP.m b/Preprocessing/timeDriftPP.m new file mode 100644 index 000000000..790865641 --- /dev/null +++ b/Preprocessing/timeDriftPP.m @@ -0,0 +1,336 @@ +function sample_data = timeDriftPP(sample_data, qcLevel, auto) +%TIMEDRIFTPP Prompts the user to apply time drift correction to the given data +% sets. A pre-deployment time offset and end deployment time offset are +% required and if included in the DDB (or CSV file), will be shown in the +% dialog box. Otherwise, user is required to enter them. +% +% Offsets should be entered in seconds from UTC time (instrumentTime - UTCtime). +% An offset at the start will result in an offset to the start time. Global +% attributes of time coverage are also adjusted. +% Time drift calculations are applied to both raw and QC datasets. +% All IMOS datasets should be provided in UTC time. Raw data may not +% necessarily have been captured in UTC time, so a correction must be made +% before the data can be considered to be in an IMOS compatible format. +% +% +% Inputs: +% sample_data - cell array of structs, the data sets to which time +% correction should be applied. +% qcLevel - string, 'raw' or 'qc'. Some pp not applied when 'raw'. +% auto - logical, run pre-processing in batch mode. +% +% Outputs: +% sample_data - same as input, with time correction applied. +% + +% +% Author: Rebecca Cowley +% Contributor: Guillaume Galibert +% + +% +% Copyright (c) 2009, eMarine Information Infrastructure (eMII) and Integrated +% Marine Observing System (IMOS). +% All rights reserved. +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions are met: +% +% * Redistributions of source code must retain the above copyright notice, +% this list of conditions and the following disclaimer. +% * Redistributions in binary form must reproduce the above copyright +% notice, this list of conditions and the following disclaimer in the +% documentation and/or other materials provided with the distribution. +% * Neither the name of the eMII/IMOS nor the names of its contributors +% may be used to endorse or promote products derived from this software +% without specific prior written permission. +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% POSSIBILITY OF SUCH DAMAGE. +% + +narginchk(2,3); + +if ~iscell(sample_data), error('sample_data must be a cell array'); end +if isempty(sample_data), return; end +% if this is the second time through (ie for applying autoQC PP +% routines), then return. sample_data already contains the corrections +% and they are therefore carried through. +if auto, return; end + +% auto logical in input to enable running under batch processing +if nargin<3, auto=false; end + +ddb = readProperty('toolbox.ddb'); + +descs = {}; +nSample = length(sample_data); +startOffsets = zeros(nSample, 1); +endOffsets = startOffsets; +sets = ones(nSample, 1); + +% create descriptions, and get timezones/offsets for each data set +for k = 1:nSample + + descs{k} = genSampleDataDesc(sample_data{k}); + + %check to see if the offsets are available already from the ddb + if strcmp('csv',ddb) + if isfield(sample_data{k}.meta.deployment,'StartOffset') + startOffsets(k) = sample_data{k}.meta.deployment.StartOffset; + end + end + + if strcmp('csv',ddb) + if isfield(sample_data{k}.meta.deployment,'EndOffset') + endOffsets(k) = sample_data{k}.meta.deployment.EndOffset; + end + else + if all(isfield(sample_data{k}.meta.deployment,{'TimeDriftInstrument', 'TimeDriftGPS'})) + if ~isempty(sample_data{k}.meta.deployment.TimeDriftInstrument) && ~isempty(sample_data{k}.meta.deployment.TimeDriftGPS) + endOffsets(k) = (sample_data{k}.meta.deployment.TimeDriftInstrument - sample_data{k}.meta.deployment.TimeDriftGPS)*3600*24; + end + end + end +end + +f = figure(... + 'Name', 'Time start and end drift calculations in seconds',... + 'Visible', 'off',... + 'MenuBar' , 'none',... + 'Resize', 'off',... + 'WindowStyle', 'Modal',... + 'NumberTitle', 'off'); + +cancelButton = uicontrol('Style', 'pushbutton', 'String', 'Cancel'); +confirmButton = uicontrol('Style', 'pushbutton', 'String', 'Ok'); + +setCheckboxes = []; +startOffsetFields = []; +endOffsetFields = []; + +for k = 1:nSample + + setCheckboxes(k) = uicontrol(... + 'Style', 'checkbox',... + 'String', descs{k},... + 'Value', 1, ... + 'UserData', k); + + startOffsetFields(k) = uicontrol(... + 'Style', 'edit',... + 'UserData', k, ... + 'String', num2str(startOffsets(k))); + + endOffsetFields(k) = uicontrol(... + 'Style', 'edit',... + 'UserData', k, ... + 'String', num2str(endOffsets(k))); + +end + +% set all widgets to normalized for positioning +set(f, 'Units', 'normalized'); +set(cancelButton, 'Units', 'normalized'); +set(confirmButton, 'Units', 'normalized'); +set(setCheckboxes, 'Units', 'normalized'); +set(startOffsetFields, 'Units', 'normalized'); +set(endOffsetFields, 'Units', 'normalized'); + +set(f, 'Position', [0.2 0.35 0.6 0.0222*nSample]); +set(cancelButton, 'Position', [0.0 0.0 0.5 0.1]); +set(confirmButton, 'Position', [0.5 0.0 0.5 0.1]); + +rowHeight = 0.9 / nSample; +for k = 1:nSample + + rowStart = 1.0 - k * rowHeight; + + set(setCheckboxes (k), 'Position', [0.0 rowStart 0.6 rowHeight]); + set(startOffsetFields (k), 'Position', [0.6 rowStart 0.2 rowHeight]); + set(endOffsetFields (k), 'Position', [0.8 rowStart 0.2 rowHeight]); +end + +% set back to pixels +set(f, 'Units', 'normalized'); +set(cancelButton, 'Units', 'normalized'); +set(confirmButton, 'Units', 'normalized'); +set(setCheckboxes, 'Units', 'normalized'); +set(startOffsetFields, 'Units', 'normalized'); +set(endOffsetFields, 'Units', 'normalized'); + +% set widget callbacks +set(f, 'CloseRequestFcn', @cancelCallback); +set(f, 'WindowKeyPressFcn', @keyPressCallback); +set(setCheckboxes, 'Callback', @checkboxCallback); +set(startOffsetFields, 'Callback', @startoffsetFieldCallback); +set(endOffsetFields, 'Callback', @endoffsetFieldCallback); +set(cancelButton, 'Callback', @cancelCallback); +set(confirmButton, 'Callback', @confirmCallback); + +set(f, 'Visible', 'on'); + +uiwait(f); + +% calculate the drift and apply to the selected datasets +for k = 1:nSample + + % this set has been deselected + if ~sets(k), continue; end + + %check for zero values in both fields + if startOffsets(k) == 0 && endOffsets(k) == 0 + continue + end + + % look time through dimensions + type = 'dimensions'; + timeIdx = getVar(sample_data{k}.(type), 'TIME'); + + if timeIdx == 0 + % look time through variables + type = 'variables'; + timeIdx = getVar(sample_data{k}.(type), 'TIME'); + end + + % no time dimension nor variable in this dataset + if timeIdx == 0, continue; end + + timeDriftComment = ['timeDriftPP: TIME values and time_coverage_end global attributes have been have been '... + 'linearly adjusted for an offset of: ' num2str(startOffsets(k)) ' seconds and a drift of: ' num2str(endOffsets(k) - startOffsets(k)) ' seconds ' ... + 'across the deployment.']; + + % apply the drift correction + newtime = timedrift_corr(sample_data{k}.(type){timeIdx}.data, ... + startOffsets(k),endOffsets(k)); + sample_data{k}.(type){timeIdx}.data = newtime; + + % and to the time coverage atttributes + sample_data{k}.time_coverage_start = newtime(1); + sample_data{k}.time_coverage_end = ... + newtime(end); + + + comment = sample_data{k}.(type){timeIdx}.comment; + if isempty(comment) + sample_data{k}.(type){timeIdx}.comment = timeDriftComment; + else + sample_data{k}.(type){timeIdx}.comment = [comment ' ' timeDriftComment]; + end + + history = sample_data{k}.history; + if isempty(history) + sample_data{k}.history = sprintf('%s - %s', datestr(now_utc, ... + readProperty('exportNetCDF.dateFormat')), timeDriftComment); + else + sample_data{k}.history = sprintf('%s\n%s - %s', history, ... + datestr(now_utc, readProperty('exportNetCDF.dateFormat')), timeDriftComment); + end +end + + function keyPressCallback(source,ev) + %KEYPRESSCALLBACK If the user pushes escape/return while the dialog has + % focus, the dialog is cancelled/confirmed. This is done by delegating + % to the cancelCallback/confirmCallback functions. + % + if strcmp(ev.Key, 'escape'), cancelCallback( source,ev); + elseif strcmp(ev.Key, 'return'), confirmCallback(source,ev); + end + end + + function cancelCallback(source,ev) + %CANCELCALLBACK Cancel button callback. Discards user input and closes the + % dialog . + % + sets(:) = 0; + startOffsets(:) = 0; + delete(f); + end + + function confirmCallback(source,ev) + %CONFIRMCALLBACK. Confirm button callback. Closes the dialog. + % + delete(f); + end + + function checkboxCallback(source, ev) + %CHECKBOXCALLBACK Called when a checkbox selection is changed. + % Enables/disables the offset text field. + % + idx = get(source, 'UserData'); + val = get(source, 'Value'); + + sets(idx) = val; + + if val, val = 'on'; + else val = 'off'; + end + + set(startOffsetFields(idx), 'Enable', val); + + end + + function startoffsetFieldCallback(source, ev) + %OFFSETFIELDCALLBACK Called when the user edits one of the offset fields. + % Verifies that the text entered is a number. + % + + val = get(source, 'String'); + idx = get(source, 'UserData'); + + val = str2double(val); + + % reset the offset value on non-numerical + % input, otherwise save the new value + if isnan(val), set(source, 'String', num2str(startOffsets(idx))); + else startOffsets(idx) = val; + + end + end + + function endoffsetFieldCallback(source, ev) + %OFFSETFIELDCALLBACK Called when the user edits one of the offset fields. + % Verifies that the text entered is a number. + % + + val = get(source, 'String'); + idx = get(source, 'UserData'); + + val = str2double(val); + + % reset the offset value on non-numerical + % input, otherwise save the new value + if isnan(val), set(source, 'String', num2str(endOffsets(idx))); + else endOffsets(idx) = val; + + end + end + + function newtime = timedrift_corr(time,offset_s,offset_e) + %remove linear drift of time (in days) from any instrument. + %the drift is calculated using the start offset (offset_s in seconds) and the + % end offset (offset_e in seconds). + %Bec Cowley, April 2014 + + % change the offset times to days: + offset_e = offset_e/60/60/24; + offset_s = offset_s/60/60/24; + + %make an array of time corrections using the offsets: + tarray = (offset_s:(offset_e-offset_s)/(length(time)-1):offset_e)'; + if isempty(tarray) + newtime = []; + else + newtime = time - tarray; + end + end +end \ No newline at end of file diff --git a/Util/fastScatterMesh.m b/Util/fastScatterMesh.m new file mode 100644 index 000000000..5e4c450fe --- /dev/null +++ b/Util/fastScatterMesh.m @@ -0,0 +1,97 @@ +function [ plotH ] = fastScatterMesh( hAx, xdata, ydata, cdata, colLimits, varargin ) +%FASTSCATTERMESH fast scatter plot using mesh plot with simple zbuffering +%of markers +% hAx : axis handle +% xdata : x data +% ydata : y data +% cdata : data used to color markers +% colLimits : min/max cdata + +% Posting by Boris Babic for the mesh method, see +% http://www.mathworks.com/matlabcentral/newsreader/view_thread/22966 +% as referenced in +% http://au.mathworks.com/matlabcentral/fileexchange/47205-fastscatter-m +% http://au.mathworks.com/matlabcentral/fileexchange/53580-fastscatterm + +axes(hAx); +cmap = colormap(hAx); + +nColors = length(cmap); + +minClim = colLimits(1); +maxClim = colLimits(2); + +% Posting by Boris Babic for the method. see http://www.mathworks.com/matlabcentral/newsreader/view_thread/22966 +% as referenced in +% http://au.mathworks.com/matlabcentral/fileexchange/47205-fastscatter-m +% http://au.mathworks.com/matlabcentral/fileexchange/53580-fastscatterm + +% index of valid data +ix=find(isfinite(cdata + xdata + ydata)); + +if isempty(ix) | numel(ix) < 2 + hwin = []; + return; +end + +if mod(length(ix),2)==1 + ix(end+1)=ix(end); +end + +ixLength = length(ix); + +% normalize cdata +normData = (cdata(ix) - minClim) / ( maxClim - minClim ); +[~, idx] = histc(normData, 0: 1/nColors : 1); + +% simple zbuffering of markers +% 'ascending', 'descending', 'triangle', 'vee', 'flat', 'parabolic', +% 'hamming', 'hann' +try + zType = lower(readProperty('visualQC.zbuffer')); +catch e + zType = 'triangle'; +end + +switch zType + case 'ascending', + zBuffer = linspace(0, 1, ixLength); + case 'descending', + zBuffer = linspace(1, 0, ixLength) ; + case 'triangle', + zBuffer = [linspace(0, 1, ixLength/2) linspace(1, 0, ixLength/2)]; + case 'vee', + zBuffer = [linspace(1, 0, ixLength/2) linspace(0, 1, ixLength/2)]; + case 'flat', + zBuffer = zeros(size(ix)); + case 'parabolic' + jj = (0:ixLength-1)'; + zBuffer = 1 - (2*jj/ixLength - 1).^2; + case 'hamming' + thalf=pi/ixLength*((1-ixLength):2:0)'; % first half sample locations + hwin=.54+.46*cos(thalf); % first half window + zBuffer=[hwin; hwin(floor(ixLength/2):-1:1)]; % full window + case 'hann' + thalf=pi/ixLength*((1-ixLength):2:0)'; % first half sample locations + hwin=.5+.5*cos(thalf); % first half window + zBuffer=[hwin; hwin(floor(ixLength/2):-1:1)]; % full window + otherwise + warning('Unknown marker zbuffer method, using flat'); + zBuffer = zeros(size(ix)); +end + +zBuffer = zBuffer(idx); +if mod(length(ix),2)==1 + zBuffer(:,end+1)=zBuffer(:,end); +end + +ix2D=reshape(ix,2,[]); + +plotH = mesh(xdata(ix2D),ydata(ix2D),reshape(zBuffer,2,[]),double(cdata(ix2D)),... + 'EdgeColor','none', 'MarkerEdgeColor','flat', 'FaceColor','none',... + varargin{:}); + +view(2); %sets the default 2-D view for mesh plot + +end + diff --git a/Util/fsearch.m b/Util/fsearch.m index 22c193149..60e038428 100644 --- a/Util/fsearch.m +++ b/Util/fsearch.m @@ -89,7 +89,14 @@ case 'dirs' if ~d.isdir, continue; end if ~isempty(strfind(lower(fullfile(root, d.name)), lower(pattern))) - hits{end+1} = fullfile(root, d.name); + % we check that the radical name is the one we're + % looking for. We want 4T3993 to match path/4T3993/ but not + % path/454T3993 + [~, foundName, foundExt] = fileparts(d.name); + [~, patternName, patternExt] = fileparts(pattern); + if strcmpi(foundName, patternName) + hits{end+1} = fullfile(root, d.name); + end end case 'files' if d.isdir @@ -98,12 +105,27 @@ hits = [hits subhits]; else if ~isempty(strfind(lower(fullfile(root, d.name)), lower(pattern))) - hits{end+1} = fullfile(root, d.name); + % we check that the radical name is the one we're + % looking for. We want 4T3993 to match 4T3993.DAT or + % 4T3993.csv but not 454T3993.DAT + [~, foundName, foundExt] = fileparts(d.name); + [~, patternName, patternExt] = fileparts(pattern); + if strcmpi(foundName, patternName) + hits{end+1} = fullfile(root, d.name); + end end end otherwise % both if ~isempty(strfind(lower(fullfile(root, d.name)), lower(pattern))) - hits{end+1} = fullfile(root, d.name); + % we check that the radical name is the one we're + % looking for. We want 4T3993 to match path/4T3993/ or + % path/4T3993.DAT but not path/454T3993 nor + % path/454T3993.DAT + [~, foundName, foundExt] = fileparts(d.name); + [~, patternName, patternExt] = fileparts(pattern); + if strcmpi(foundName, patternName) + hits{end+1} = fullfile(root, d.name); + end end if d.isdir diff --git a/Util/genSampleDataDesc.m b/Util/genSampleDataDesc.m index 07f350354..3200d9a9c 100644 --- a/Util/genSampleDataDesc.m +++ b/Util/genSampleDataDesc.m @@ -49,7 +49,7 @@ timeFmt = readProperty('toolbox.timeFormat'); -timeRange = [datestr(sam.time_coverage_start, timeFmt) ' - ' ... +timeRange = ['from ' datestr(sam.time_coverage_start, timeFmt) ' to ' ... datestr(sam.time_coverage_end, timeFmt)]; [fPath fName fSuffix] = fileparts(sam.toolbox_input_file); @@ -57,8 +57,9 @@ fName = [fName fSuffix]; desc = [ sam.meta.site_id ... - ' (' sam.meta.instrument_make ... + ' - ' sam.meta.instrument_make ... ' ' sam.meta.instrument_model ... - ' ' sam.meta.instrument_serial_no ... - ' - ' fName .... - ') ' timeRange]; + ' SN=' sam.meta.instrument_serial_no ... + ' @' num2str(sam.meta.depth) 'm' ... + ' ' timeRange ... + ' (' fName ')']; diff --git a/Util/getDeployments.m b/Util/getDeployments.m index 040d62911..dfabee25e 100644 --- a/Util/getDeployments.m +++ b/Util/getDeployments.m @@ -1,4 +1,4 @@ -function [fieldTrip deployments sites dataDir] = getDeployments(auto) +function [fieldTrip deployments sites dataDir] = getDeployments(auto, isCSV) %GETDEPLOYMENTS Prompts the user for a field trip ID and data directory. % Retrieves and returns the field trip, all deployments from the DDB that % are related to the field trip, and the selected data directory. @@ -7,6 +7,8 @@ % auto - if true, the user is not prompted to select a field % trip/directory; the values in toolboxProperties are % used. +% isCSV - optional [false = default]. If true, look for csv files +% rather than using database % % Outputs: % fieldTrip - field trip struct - the field trip selected by the user. @@ -49,13 +51,25 @@ % ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE % POSSIBILITY OF SUCH DAMAGE. % +if nargin == 1 + isCSV = false; +end + deployments = struct; sites = struct; +%check for CSV file import: +ddb = readProperty('toolbox.ddb'); +if strcmp(ddb,'csv') + isCSV = true; +else + isCSV = false; +end + % prompt the user to select a field trip and % directory which contains raw data files if ~auto - [fieldTrip dataDir] = startDialog('timeSeries'); + [fieldTrip, dataDir] = startDialog('timeSeries', isCSV); % if automatic, just get the defaults from toolboxProperties.txt else dataDir = readProperty('startDialog.dataDir.timeSeries'); @@ -72,15 +86,21 @@ fId = fieldTrip.FieldTripID; -% query the ddb for all deployments related to this field trip -deployments = executeDDBQuery('DeploymentData', 'EndFieldTrip', fId); +if isCSV + executeQueryFunc = @executeCSVQuery; +else + executeQueryFunc = @executeDDBQuery; +end + +% query the ddb/csv file for all deployments related to this field trip +deployments = executeQueryFunc('DeploymentData', 'EndFieldTrip', fId); % query the ddb for all sites related to these deployments lenDep = length(deployments); for i=1:lenDep if i==1 - sites = executeDDBQuery('Sites', 'Site', deployments(i).Site); + sites = executeQueryFunc('Sites', 'Site', deployments(i).Site); else - sites(i) = executeDDBQuery('Sites', 'Site', deployments(i).Site); + sites(i) = executeQueryFunc('Sites', 'Site', deployments(i).Site); end -end \ No newline at end of file +end diff --git a/Util/getpos.m b/Util/getpos.m new file mode 100644 index 000000000..19f6256ab --- /dev/null +++ b/Util/getpos.m @@ -0,0 +1,180 @@ +function [pos,unit]=getpos(h,fmt,href,opt) +% GETPOS Get graphics object position in a flexible way. +% GETPOS(H,FMT) gets the position property of graphics object +% with handle H, according to FMT that can be expressed using different +% units. H must have a "Position" property. +% +% FMT is a char array containing four "%2c" strings separated by colon or +% space. The two characters specify the unit as : +% +% px for Pixels +% nz for Normalized +% in for Inches +% cm for Centimeters +% pt for Points +% ch for Characters +% +% If FMT is only one format string from the above list, all returned values are +% expressed using this unit. +% +% Any string value of FMT can be replaced by a single '#' to not retrieve the +% corresponding value. The returned value is NaN except if the optional last +% argument OPT is set to "compact" in GETPOS(H,FMT,[HREF],OPT). +% +% Note that GETPOS(H) works as get(H,'Position') and return the position +% vector in the current unit of the graphics object H. +% +% GETPOS(H,FMT,HREF,['compact']) gets the position of the graphics object H according +% to FMT, but using the position of the graphics object HREF as reference instead +% of the parent of H. HREF must be a valid handle and must have a "Position" +% property (except for the Root object). Returned values may be negative or 0. +% +% [POS,UNIT]=GETPOS(H,...) returns an additional output argument UNIT that +% contained the unit list of the output vector position POS. It may be safer +% when different units are used. +% +% See also SETPOS, SET, GET. + +% Author: Jérôme Briot, Matlab 6.1.0.450 (R12.1) +% Contact: dutmatlab@yahoo.fr +% Revision: 1.0 (12-Feb-2007) +% 1.1 (14-Feb-2007) Third input argument HREF added. +% Minor corrections in the help section. +% 1.2 (21-Feb-2007) Bug fixed if HREF is the Root object +% Examples removed from the help section +% Comments: +% + +% Check the number of input arguments +error(nargchk(1,4, nargin)); + +% Check if H is a graphics object handle +if ~ishandle(h) + error('First argument must be a graphic object handle'); +end + +% Store the current unit of the graphics object H +current_unit=get(h,'units'); + +% Init variables +unit={current_unit current_unit current_unit current_unit}; +pos=[nan nan nan nan]; + +% If FMT input argument is not specified, works as GET(H,'Position') +if nargin==1 + pos=get(h,'position'); + return +end + +% Check if FMT is a char string +if ~ischar(fmt) + error('Second argument must be a string in GETPOS(H,FMT)') +end + +if nargin==2 % GETPOS(H,FMT) + + href=get(h,'parent'); + opt='full'; + +elseif nargin==3 + + if ishandle(href) % GETPOS(H,FMT,HREF) + + opt='full'; + + elseif strcmpi(href,'compact') % GETPOS(H,FMT,"compact") + + href=get(h,'parent'); + opt='compact'; + + else % GETPOS(H,FMT,???) + error('Wrong third argument in GETPOS(H,FMT,???). Must be a valid handle or "compact"'); + + end + +elseif nargin==4 % GETPOS(H,FMT,HREF,OPT) + + if ~ishandle(href) + error('Third argument must be a valid handle in GETPOS(H,FMT,HREF,OPT)'); + end + + if ~strcmpi(opt,'compact') + error('Last argument must be "compact" in GETPOS(H,FMT,HREF,OPT)'); + end + +end + +flag_href=0; +% Don't use HREF position if it is the parent of H +if href~=get(h,'parent') + href=h; + flag_href=1; +end + +% Store the current unit of the reference object HREF +current_ref_unit=get(href,'units'); + +% Extract 4 char strings from FMT +M=strread(fmt,'%s','delimiter',' ,'); + +% Only one FMT requested for output +if numel(M)==1 + [M{2:4}]=deal(M{1}); +end + +% List available units +available_units={'inches' 'centimeters' 'normalized' 'points' 'pixels' 'characters'}; + +% Decode elements of FMT +for n=1:numel(M) + + % If FMT(n) is not a "#" + if ~strcmp(M{n},'#') + + % Check if the units paramter is valid + idx=strcmpi(M{n},{'in' 'cm' 'nz' 'pt' 'px' 'ch'}); + + if ~any(idx) + error('Units must be one of "in", "cm", "nz", "pt", "px" or "ch"') + end + + unit{n}=available_units{idx}; % Set the units to one from the list + + end + +end + +% Get position of H using decoded FMT +for n=1:numel(M) + + % If FMT(n) is not a "#" => get the value + if ~strcmp(M{n},'#') + + % Modify the "Units" property of H + set(h,'units',unit{n}); + % Modify the "Units" property of HREF + set(href,'units',unit{n}); + % Get the current "Position" vector of H + temp=get(h,'position'); + % Get the current "Position" vector of HREF + if strcmp(get(href, 'type'), 'root') % HREF is the Root object (no 'Position' property) + temp_href=get(href,'screensize'); %%% Should be safe here ! + else temp_href=get(href,'position'); + end + % Get and store the specified field from the "Position" vector + % If HREF is specified and is not the parent of H, flag_href=1 else flag_href=0 + pos(n)=temp(n)-temp_href(n)*flag_href; + + end + +end + +% Check for compact output format +if strcmpi(opt,'compact') + pos(isnan(pos))=[]; +end + +% Restore the unit of the graphics object H +set(h,'units',current_unit); +% Restore the unit of the reference object HREF +set(href,'units',current_ref_unit); \ No newline at end of file diff --git a/Util/imosCompile.m b/Util/imosCompile.m index 02413d601..5cb2b2b78 100644 --- a/Util/imosCompile.m +++ b/Util/imosCompile.m @@ -70,7 +70,7 @@ function imosCompile() % find all .m and .mat files - these are to be % included as resources in the standalone application matlabFiles = fsearchRegexp('.m$', exportRoot, 'files'); -matlabDataFiles = fsearch('.mat', exportRoot, 'files'); +matlabDataFiles = fsearchRegexp('.mat$', exportRoot, 'files'); % we leave out imosToolbox.m because, as the main % function, it must be listed first. diff --git a/Util/imosPackage.m b/Util/imosPackage.m index cec8dd3bb..261885cb3 100644 --- a/Util/imosPackage.m +++ b/Util/imosPackage.m @@ -83,7 +83,7 @@ function imosPackage() % find all .m, .mat, .txt, .jar, .bat, .exe, .bin and .sh files - these are to be % included as resources in the standalone application matlabFiles = fsearchRegexp('.m$', toolboxRoot, 'files'); -matlabDataFiles = fsearch('.mat', toolboxRoot, 'files'); +matlabDataFiles = fsearchRegexp('.mat$', toolboxRoot, 'files'); resourceFiles = [matlabFiles matlabDataFiles]; resourceFiles = [resourceFiles fsearchRegexp('.txt$', toolboxRoot, 'files')]; resourceFiles = [resourceFiles fsearchRegexp('.COF$', toolboxRoot, 'files')]; diff --git a/Util/legendflex.m b/Util/legendflex.m new file mode 100644 index 000000000..3eed973a7 --- /dev/null +++ b/Util/legendflex.m @@ -0,0 +1,827 @@ +function varargout = legendflex(varargin) +%LEGENDFLEX Creates a more flexible legend +% +% legendflex(M, param1, val1, ...) +% legendflex(h, M, param1, val1, ...) +% [legend_h,object_h,plot_h,text_str] = legendflex(...) +% +% This offers a more flexible version of the legend command. It offers a +% different method of positioning the legend, as well as options to: +% +% - organize legend text and symbols in a grid with a specified number of +% rows and/or columns +% - rescale the horizontal space used by each legend symbol +% - create multiple legends for the same axis +% - add a title to the legend within the legend box +% +% Unlike in the default legend command, where the legend is positioned +% relative to the labeled objects' parent axis according to one of 16 +% location strings, this function positions the legend based on two anchor +% points (one on either the figure or a child object of a figure, and one +% on the legend itself) and a buffer (or offset) between these two anchor +% points. The anchor points refer to the corners and centers of each +% side of the box surrounding the reference object and the legend itself; +% they can be refered to either as numbers (1-8, clockwise from northwest +% corner) or strings ('nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'). The +% position of the legend is determined by these two points and the distance +% between them, defined in the 'buffer' variable, which by default is +% measured in pixels. So the combination of +% +% (..., 'ref', gca, 'anchor', [3 3], 'buffer', [-10 -10]) +% +% means that you want the northeast corner of the current axis to be +% aligned with the northeast corner of the legend, but with the legend +% shifted 10 pixels to the left and down. +% +% This method of positioning can be particularly useful when labeling a +% figure that includes many subplots that share a common color scheme, +% where the "best" location for a legend is not necessarily within the +% bounds of an axis. Unlike the legend command, the axes in the figure are +% never resized (and it is up to the user to check that the legend fits on +% the figure in the specified location). In addition to being easier than +% manually positioning a legend, this function updates the legend location +% when the figure is resized, preserving the desired alignment. The +% following anchor/buffer combinations, when used with the default +% reference and a buffer unit of pixels, approximately replicate the +% typical legend locations: +% +% Specifier Anchor Buffer +% +% north [2 2] [ 0 -10] +% south [6 6] [ 0 10] +% east [4 4] [-10 0] +% west [8 8] [ 10 0] +% northeast [3 3] [-10 -10] +% northwest [1 1] [ 10 -10] +% southeast [5 5] [-10 10] +% southwest [7 7] [ 10 10] +% northoutside* [2 6] [ 0 10] +% southoutside* [6 2] [ 0 -10] +% eastoutside* [3 8] [ 10 0] +% westoutside* [8 3] [-10 0] +% northeastoutside* [3 1] [ 10 0] +% northwestoutside* [1 3] [-10 0] +% southeastoutside* [5 7] [ 10 0] +% southwestoutside* [7 5] [-10 0] *placed outside axis rather +% than resizing plot box +% +% This function should support all types of plot objects. +% +% Updates to labeled line and patch properties should be reflected in the +% legend. In pre-R2014b versions of Matlab (those that use the old +% non-object graphics handles), properties of more complex legend labels, +% such as contours, quivers, bars, etc.) will also be synced to the legend; +% however, at this time, the code doesn't update properties for anything +% other than lines and patches in R2014b+ (haven't found a good way to +% listen for changes to the properties of the other graphics object types). +% +% A note on resizing: This function assigns a resize function to the parent +% figure to maintain the position of the legend (in terms of anchor +% location and buffer) as the figure size changes. If you manually resize +% the legend, this function will respect changes to height, width, and +% units (though I don't recommend changing the units to 'normalized', as +% this can cause the text and symbols to overflow the legend box on +% resize). It will not respect manual repositioning when resizing, since +% it assumes you want to maintain the anchor/buffer prescription used to +% create it. Overall, I've tried to make this resize as unobtrusive as +% possible; if your figure already has a resize function at the time you +% apply it, that behavior is inherited, with the legend-resize called +% afterward. If you plan to further modify the figure's resize function +% post-legendflex and want to maintain repositioning of the legends, +% retrieve the resize function via hfun = get(hfig, 'ResizeFcn'), pass it +% to the new resize function, and invoke it via feval(oldfun, h, ed), where +% h and ed are the default variables passed by a callback function. +% +% Input variables: +% +% M: cell array of strings, labels for legend +% +% h: handle of axis or handle(s) of object(s) to be labeled. If +% this is an axis handle, all children of the axis will be +% included in the legend. If not included, current axis is +% used. +% +% Optional input variables (passed as parameter/value pairs): [default] +% +% ncol: number of columns, or 0 to indicate as many as necessary +% given the # of labeled objects [1 if nrow is 0, 0 +% otherwise] +% +% nrow: number of rows, or 0 to indicate as many as necessary +% given the # of labeled objects [0] +% +% ref: handle of object used to position the legend. This can be +% either a figure or a child object of a figure (and does not +% need to relate in any way to the objects being labeled). +% If not included, the reference will be to the axis that a +% normal legend would be associated with (usually the parent +% axis of the labeled objects, unless objects from multiple +% axes are passed, in which case it's the parent object of +% the first labeled object). +% +% anchor: 1 x 2 array specifying which points of the reference object +% and new legend, respectively, to anchor to each other. +% Anchor points can be described using either numbers (in a 1 +% x 2 double array) or directional strings (in a 1 x 2 cell +% array) as follows: +% 1: 'nw' upper left corner +% 2: 'n' center of top edge +% 3: 'ne' upper right corner +% 4: 'e' center of right edge +% 5: 'se' bottom right corner +% 6: 's' center of bottom edge +% 7: 'sw' bottom left corner +% 8: 'w' center of left edge +% +% [[3 3], i.e. {'ne' 'ne'}] +% +% buffer: 1 x 2 array of horizontal and vertical distance, +% respectively, from the reference anchor point to the legend +% anchor point. Distance is measured in units specified by +% bufferunit. [[-10 -10]] +% +% bufferunit: unit for buffer distance. Note that this property only +% affects the units used to position the legend, not the +% units for the legend itself (which is always a fixed size, +% based on the space needed to encapsulate the specified +% symbols and text). The 'normalized' units are normalized +% to size of the figure. ['pixels'] +% +% box: 'on' or 'off', specifies whether to enclose legend objects +% in a box ['on'] +% +% xscale: scalar value indicating scale factor to apply to the width +% required by each symbol, relative to the size used by +% legend. For example, 0.5 will shorten the lines/patches by +% half. [1] +% +% title: A title string to be added inside the legend box, centered, +% above all legend entries. This can be either a string or a +% cell array of strings; the latter will produce a multi-line +% title. If empty, no title is added. [''] +% +% padding: 1 x 3 array, pixel spacing added to beginning of each +% column (before symbol), between symbol and text, and after +% text, respectively. Usually, the default provides the +% spacing typical of a regular legend, but occassionally the +% extent properties wrap a little too close to text, making +% things look crowded; in these cases you can try unsquishing +% things via this parameter. [2 1 1] +% +% In addition to these legendflex-specific parameters, this function will +% accept any parameter accepted by the original legend function (e.g. +% font properties) except 'location', 'boxon', 'boxoff', or 'hide'. +% +% Output variables: +% +% legend_h: handle of the legend axis. It is not linked to an axis or +% graphics objects in the same way as a Matlab legend. +% However, on figure resize, all properties of the legend +% objects are checked for changes, so adjusting the figure +% size can re-link the legend to the labeled objects after +% you have made changes to those objects. +% +% object_h: handles of the line, patch, and text graphics objects +% created in the legend +% +% plot_h: handles of the lines and other objects labeled in this +% legend +% +% text_str: cell array of the text strings used in the legend +% +% +% Example: +% +% % Replicating an example from legend.m: +% +% figure; +% b = bar(rand(10,5),'stacked'); colormap(summer); hold on +% x = plot(1:10,5*rand(10,1),'marker','square','markersize',12,... +% 'markeredgecolor','y','markerfacecolor',[.6 0 .6],... +% 'linestyle','-','color','r','linewidth',2); hold off +% lbl = {'Carrots','Peas','Peppers','Green Beans','Cucumbers','Eggplant'}; +% +% % Rather than covering up data or resizing the axis, let's squeeze the +% % legend into the margin at the top of the figure; +% +% legendflex([b,x], lbl, 'ref', gcf, ... +% 'anchor', {'n','n'}, ... +% 'buffer',[0 0], ... +% 'nrow',2, ... +% 'fontsize',8); + +% Copyright 2011-2014 Kelly Kearney + +% Detemine whether HG2 is in use + +hg2flag = ~verLessThan('matlab', '8.4.0'); + +%------------------- +% Parse input +%------------------- +% +% allinput = varargin; % Save for callback later +% +% islegin = false(size(varargin)); + +% First inputs must be either: +% (M, ...) +% (h, M, ...) + +narginchk(1,Inf); + +% Split input into the variables that will be passed to legend (handles and +% labels) and everything else + +handlepassed = all(ishandle(varargin{1})); % for HG1/HG2 + +if handlepassed + legin = varargin(1:2); + if ~iscell(legin{2}) || ~all(cellfun(@ischar, legin{2})) + error('Legend labels must be a cell array of strings'); + end + pv = varargin(3:end); +else + legin = varargin(1); + if ~iscell(legin{1}) || ~all(cellfun(@ischar, legin{1})) + if isnumeric(legin{1}) + error('Unable to parse input 1; check that handle(s) exist'); + else + error('Legend labels must be a cell array of strings'); + end + end + pv = varargin(2:end); +end + +% Parse my optional properties + +if hg2flag + defref = gobjects(0); +else + defref = NaN; +end + +p = inputParser; +p.addParamValue('xscale', 1, @(x) validateattributes(x, {'numeric'}, {'nonnegative','scalar'})); +p.addParamValue('ncol', 0, @(x) validateattributes(x, {'numeric'}, {'scalar', 'integer'})); +p.addParamValue('nrow', 0, @(x) validateattributes(x, {'numeric'}, {'scalar', 'integer'})); +p.addParamValue('ref', defref, @(x) validateattributes(x, {'numeric','handle'}, {'scalar'})); +p.addParamValue('anchor', [3 3], @(x) validateattributes(x, {'numeric','cell'}, {'size', [1 2]})); +p.addParamValue('buffer', [-10 -10], @(x) validateattributes(x, {'numeric'}, {'size', [1 2]})); +p.addParamValue('bufferunit', 'pixels', @(x) validateattributes(x, {'char'}, {})); +p.addParamValue('box', 'on', @(x) validateattributes(x, {'char'}, {})); +p.addParamValue('title', '', @(x) validateattributes(x, {'char','cell'}, {})); +p.addParamValue('padding', [2 1 1], @(x) validateattributes(x, {'numeric'}, {'nonnegative', 'size', [1 3]})); + +p.KeepUnmatched = true; + +p.parse(pv{:}); +Opt = p.Results; + +% Any parameters that don't match mine are assumed to be a legend property. +% If not, legend will handle the error when I call it. + +Extra = p.Unmatched; +extra = [fieldnames(Extra) struct2cell(Extra)]; +extra = extra'; + +% Validate that units and box inputs are correct + +validatestring(Opt.bufferunit, {'pixels','normalized','inches','centimeters','points','characters'}, 'legendflex', 'bufferunit'); +validatestring(Opt.box, {'on', 'off'}, 'legendflex', 'box'); + +% Translate anchor strings to numbers, if necessary + +if iscell(Opt.anchor) + [blah, Opt.anchor] = ismember(Opt.anchor, {'nw','n','ne','e','se','s','sw','w'}); + if ~all(blah) + error('Anchor must be 1 x 2 cell array of strings: n, e, s, w, ne, nw, se, sw'); + end +else + validateattributes(Opt.anchor, {'numeric'}, {'integer', '<=', 8}, 'legendflex', 'anchor'); +end + +% Create a temporary legend to get all the objects + +S = warning('off', 'MATLAB:legend:PlotEmpty'); +[h.leg, h.obj, h.labeledobj, h.textstr] = legend(legin{:}, extra{:}, 'location', 'northeast'); +nobj = length(h.labeledobj); +warning(S); + +if nobj == 0 + warning('Plot empty; no legend created'); + return +end + +% There's a bug in R2014b-R2015a that causes rendering issues if a contour +% object is included in a legend and legend is called with more than one +% output. For some reason, the rendering issues disappear only if the +% contour object(s) is listed last in the legend. So for now, my +% workaround for this is to change the order of the legend labels as +% necessary. + +iscont = strcmp(get(h.labeledobj, 'type'), 'contour'); +cbugflag = ~verLessThan('matlab', '8.4.0') && any(iscont); +if cbugflag + + if length(legin) == 1 + legin = {h.labeledobj legin{1}}; + end + + delete(h.leg); + + [srt, isrt] = sort(iscont); + legin{1} = legin{1}(isrt); + legin{2} = legin{2}(isrt); + + [h.leg, h.obj, h.labeledobj, h.textstr] = legend(legin{:}, extra{:}, 'location', 'northeast'); + +end + +% # rows and columns + +if (Opt.ncol == 0) && (Opt.nrow == 0) + Opt.ncol = 1; + Opt.nrow = nobj; +elseif (Opt.ncol == 0) + Opt.ncol = ceil(nobj./Opt.nrow); +elseif (Opt.nrow == 0) + Opt.nrow = ceil(nobj./Opt.ncol); +end +if Opt.ncol*Opt.nrow < nobj + error('Number of legend entries greater than specified grid allows; change ncol and/or nrow'); +end + +% Reference object + +if hg2flag + + if isempty(Opt.ref) + + if all(ishandle(legin{1})) + tmp = ancestor(legin{1}, 'axes'); + if iscell(tmp) + Opt.ref = tmp{1}; + else + Opt.ref = tmp(1); + end + else + Opt.ref = gca; + end + + end +else + if isnan(Opt.ref) + tmp = get(h.leg, 'UserData'); + Opt.ref = tmp.PlotHandle; + end +end +if ~ishandle(Opt.ref) + error('Input ref must be a graphics handle'); +end + +% Box + +Opt.box = strcmpi('on', Opt.box); + +% Convert units to getpos abbreviations + +unittable = {... + 'px' 'Pixels' + 'nz' 'Normalized' + 'in' 'Inches' + 'cm' 'Centimeters' + 'pt' 'Points' + 'ch' 'Characters'}; +Opt.bufunit = unittable{strcmpi(unittable(:,2),Opt.bufferunit),1}; + +% Check for title + +addtitle = ~isempty(Opt.title); + +%------------------- +% New placement of +% everything in +% legend +%------------------- + +% Determine parent figure + +figh = ancestor(Opt.ref, 'figure'); +currax = get(figh, 'currentaxes'); + +% Calculate row height + +legpospx = getpos(h.leg, 'px'); + +% rowHeight = legpospx(4)/nobj; +vmarginNm = 0.275/nobj; +vmarginPx = legpospx(4) * vmarginNm; + +rowHeightNm = (1 - vmarginNm)/nobj; +rowHeight = rowHeightNm .* legpospx(4); + +% Determine width needed for each text string + +if nobj == 1 + textExtent = get(h.obj(1:nobj), 'Extent'); +else + textExtent = cell2mat(get(h.obj(1:nobj), 'Extent')); +end +textWidthPx = textExtent(:,3) .* legpospx(3); +textHeightPx = textExtent(:,4) .* legpospx(4); +textWidthNm = textExtent(:,3); + +% Calculate horizontal space needed for symbols + +symbolWidthPx = textExtent(1,1) .* legpospx(3) * Opt.xscale; +symbolWidthNm = textExtent(1,1); + +% Calculate column width needed for 2px-symbol-1px-text-1px + +colWidth = zeros(Opt.ncol*Opt.nrow,1); +colWidth(1:nobj) = textWidthPx + symbolWidthPx + sum(Opt.padding); +colWidth = reshape(colWidth, Opt.nrow, Opt.ncol); +colWidth = max(colWidth,[],1); + +% If title is added, figure out how much space it will need + +if addtitle + textProps = {'FontAngle','FontName','FontSize','FontUnits','FontWeight','Interpreter'}; + textVals = get(h.obj(1), textProps); + ttlprops = [textProps; textVals]; + + fpos = getpos(figh, 'px'); + figtmp = figure('units','pixels','position',[0 0 fpos(3:4)],'visible','off'); + axes('parent',figtmp,'position',[0 0 1 1],'xlim',[0 fpos(3)],'ylim',[0 fpos(4)]); + tmp = text(0,0,Opt.title, ttlprops{:}, 'horiz', 'left', 'vert', 'bottom'); + ttlex = get(tmp, 'extent'); + ttlwidth = ceil(ttlex(3)) + 4; % Add a little padding + ttlheight = ceil(ttlex(4)); + + if ttlwidth > sum(colWidth) + colWidth(end) = colWidth(end) + (ttlwidth-sum(colWidth)); + end + close(figtmp); +end + +% Locate bottom left corner of each legend symbol, text box, and title + +xsymbnew = [0 cumsum(colWidth(1:end-1))]+Opt.padding(1); +ysymbnew = (rowHeight*Opt.nrow + vmarginPx)-(1:Opt.nrow)*rowHeight; +[xsymbnew, ysymbnew] = meshgrid(xsymbnew, ysymbnew); +xsymbnew = xsymbnew(1:nobj); +ysymbnew = ysymbnew(1:nobj); + +xtext = xsymbnew + Opt.padding(2) + symbolWidthPx; +ytext = ysymbnew;% + 1; + +xsymbold = zeros(nobj,1); +ysymbold = 1 - (1/nobj)*(1:nobj); + +wnewleg = sum(colWidth); +hnewleg = rowHeight*Opt.nrow + vmarginPx; + +if addtitle + xttl = wnewleg/2; + yttl = hnewleg; + hnewleg = hnewleg + ttlheight; +end + +% Get legend position in bufferunit and translate to pixels + +legpos = positionleg(Opt.ref, wnewleg, hnewleg, Opt.anchor, Opt.buffer, Opt.bufunit); +tmpax = axes('units', Opt.bufferunit, 'position', legpos,'visible','off'); +legpos = getpos(tmpax, 'px'); +delete(tmpax); + +%------------------- +% Create legend +%------------------- + +% Create the legend axis + +hnew.leg = axes('units', 'pixels', ... + 'position', legpos, ... + 'xlim', [0 legpos(3)], ... + 'ylim', [0 legpos(4)], ... + 'xtick', [], ... + 'ytick', [], ... + 'box', 'on', ... + 'parent', figh); + +% Copy the text strings to the new legend + +textProps = {'FontAngle','FontName','FontSize','FontUnits','FontWeight','Interpreter','HorizontalAlignment','VerticalAlignment'}; +textVals = get(h.obj(1:nobj), textProps); + +if hg2flag + hnew.obj = gobjects(size(h.obj)); +else + hnew.obj = zeros(size(h.obj)); +end +for it = 1:nobj + props = [textProps; textVals(it,:)]; + hnew.obj(it) = text(xtext(it), ytext(it), h.textstr{it}, props{:}, ... + 'horizontalalignment', 'left', ... + 'verticalalignment', 'bottom'); +end + +% Copy the symbols to the new legend + +nsymbol = length(h.obj) - nobj; + +for ii = 1:nsymbol + + if strcmp(get(h.obj(nobj+ii), 'type'), 'hggroup') + + tag = get(h.obj(nobj+ii),'Tag'); + if ~isempty(tag) + [blah, idx] = ismember(tag,h.textstr); + end + + chld = findall(h.obj(nobj+ii), 'type', 'line', '-or', 'type', 'patch'); + for ic = 1:length(chld) + xy = get(chld(ic), {'xdata', 'ydata'}); + + xnorm = xy{1}./symbolWidthNm; + ynorm = (xy{2}- (1-idx*rowHeightNm))./rowHeightNm; + + xnew = xnorm * symbolWidthPx + xsymbnew(idx); + ynew = ynorm * rowHeight + ysymbnew(idx); + + set(chld(ic), 'xdata', xnew, 'ydata', ynew); + end + + hnew.obj(nobj+ii) = copyobj(h.obj(nobj+ii), hnew.leg); + + else + + hnew.obj(nobj+ii) = copyobj(h.obj(nobj+ii), hnew.leg); + + tag = get(h.obj(nobj+ii),'Tag'); + if ~isempty(tag) % assumes empty tags indicate repetition of previous tag (true pre-2014b) + [blah, idx] = ismember(tag,h.textstr); + end + + xy = get(h.obj(nobj+ii), {'xdata', 'ydata'}); + + xnorm = xy{1}./symbolWidthNm; + ynorm = (xy{2}- (1-idx*rowHeightNm))./rowHeightNm; + + xnew = xnorm * symbolWidthPx + xsymbnew(idx); + ynew = ynorm * rowHeight + ysymbnew(idx); + + set(hnew.obj(nobj+ii), 'xdata', xnew, 'ydata', ynew); + + end + +end + +% Add title + +if addtitle + text(xttl, yttl, Opt.title, ttlprops{:}, 'horiz', 'center', 'vert', 'bottom'); +end + +% Add box or hide axis + +if Opt.box + set(hnew.leg, 'box', 'on'); +else + set(hnew.leg, 'visible', 'off'); +end + +% Delete the temporary legend + +delete(h.leg); + +% Return focus to previously-current axis + +set(figh, 'currentaxes', currax); +drawnow; % Not sure why this is necessary for the currentaxes to take effect, but it is + +%------------------- +% Callbacks and +% listeners +%------------------- + +% Save some relevant variables in the new legend axis's application data + +Lf.ref = Opt.ref; +Lf.w = wnewleg; +Lf.h = hnewleg; +Lf.anchor = Opt.anchor; +Lf.buffer = Opt.buffer; +Lf.bufunit = Opt.bufunit; +Lf.bufferunit = Opt.bufferunit; +Lf.plotobj = h.labeledobj; +Lf.legobj = hnew.obj; + +setappdata(hnew.leg, 'legflex', Lf); + +% Resize listeners + +addlistener(hnew.leg, 'Position', 'PostSet', @(src,evt) updatelegappdata(src,evt,hnew.leg)); +if hg2flag && strcmp(Lf.ref.Type, 'figure') + addlistener(Lf.ref, 'SizeChanged', @(src,evt) updatelegpos(src,evt,hnew.leg)); +else + addlistener(Lf.ref, 'Position', 'PostSet', @(src,evt) updatelegpos(src,evt,hnew.leg)); +end +rsz = get(figh, 'ResizeFcn'); +if isempty(rsz) % No previous resize function + set(figh, 'ResizeFcn', @updatelegfigresize); +else + if ~iscell(rsz) + rsz = {rsz}; + end + hasprev = cellfun(@(x) isequal(x, @updatelegfigresize), rsz); + if ~hasprev + rsz = {rsz{:} @updatelegfigresize}; + set(figh, 'ResizeFcn', {@wrapper, rsz}); + end +end + +% Run the resync function if anything changes with the labeled objects + +objwatch = findall(h.labeledobj, 'type', 'line', '-or', 'type', 'patch'); + +for ii = 1:length(objwatch) + switch lower(get(objwatch(ii), 'type')) + case 'line' + triggerprops = {'Color','LineStyle','LineWidth','Marker','MarkerSize','MarkerEdgeColor','MarkerFaceColor'}; + addlistener(objwatch(ii), triggerprops, 'PostSet', @(h,ed) resyncprops(h,ed,hnew.leg)); + case 'patch' + triggerprops = {'CData','CDataMapping','EdgeAlpha','EdgeColor','FaceAlpha','FaceColor','LineStyle','LineWidth','Marker','MarkerEdgeColor','MarkerFaceColor','MarkerSize'}; + addlistener(objwatch(ii), triggerprops, 'PostSet', @(h,ed) resyncprops(h,ed,hnew.leg)); + end +end + + +%------------------- +% Output +%------------------- + +out = {hnew.leg, hnew.obj, h.labeledobj, h.textstr}; +varargout = out(1:nargout); + + +%***** Subfunctions ***** + +%------------------------ +% Position new legend +%------------------------ + +function legpos = positionleg(href, w, h, anchor, buffer, bufunit) +% ap: position vector for reference object +% lp: position vector for legend + +if strcmp(get(href, 'type'), 'figure') + tmp = axes('parent', href,'position', [0 0 1 1],'visible','off'); + pos = getpos(tmp, bufunit); + delete(tmp); +else + pos = getpos(href, bufunit); +end + +htmp = axes('units', 'pixels', 'position', [0 0 w h], 'visible','off'); +lpos = getpos(htmp, bufunit); +delete(htmp); +w = lpos(3); +h = lpos(4); + +% Find anchor locations on reference object + +refxy = [... + pos(1) pos(2)+pos(4) + pos(1)+pos(3)/2 pos(2)+pos(4) + pos(1)+pos(3) pos(2)+pos(4) + pos(1)+pos(3) pos(2)+pos(4)/2 + pos(1)+pos(3) pos(2) + pos(1)+pos(3)/2 pos(2) + pos(1) pos(2) + pos(1) pos(2)+pos(4)/2]; + +% How bottom left relates to each anchor point + +shift = [... + 0 -h + -w/2 -h + -w -h + -w -h/2 + -w 0 + -w/2 0 + 0 0 + 0 -h/2]; + +% Legend location + +corner = refxy(anchor(1),:) + buffer + shift(anchor(2),:); +legpos = [corner w h]; + +%------------------------ +% Listener functions +%------------------------ + +% If user manually resizes the legend, update the app data + +function updatelegappdata(src, evt, legax) +if ishandle(legax) + Lf = getappdata(legax, 'legflex'); + pos = getpos(legax, 'px'); + Lf.w = pos(3); + Lf.h = pos(4); + setappdata(legax, 'legflex', Lf); +end +% If reference object moves or resizes, reposition the legend appropriately + +function updatelegpos(src, evt, legax) +if ishandle(legax) + Lf = getappdata(legax, 'legflex'); + legpos = positionleg(Lf.ref, Lf.w, Lf.h, Lf.anchor, Lf.buffer, Lf.bufunit); + set(legax, 'Units', Lf.bufferunit, 'Position', legpos); +end + +% Since figure resize can change axis size without actually triggering a +% listener, force this + +function updatelegfigresize(src, evt) + +allax = findall(src, 'type', 'axes'); +for ii = 1:length(allax) + isleg = ~isempty(getappdata(allax(ii), 'legflex')); + if ~isleg + pos = get(allax(ii), 'Position'); + set(allax(ii), 'Position', pos); % No change, just trigger PostSet + end +end + +% If plotted object changes, resync with legend + +function resyncprops(src, evt, legax) + +if ishandle(legax) % In case it's been deleted + + Lf = getappdata(legax, 'legflex'); + + str = cellstr(num2str((1:length(Lf.plotobj))')); + [htmp.leg, htmp.obj, htmp.labeledobj, htmp.textstr] = legend(Lf.plotobj, str); + + objtype = get(Lf.legobj, 'type'); + isline = strcmp(objtype, 'line'); + ispatch = strcmp(objtype, 'patch'); + ishg = strcmp(objtype, 'hggroup'); + hgidx = find(ishg); + + lobj = [Lf.legobj(isline) htmp.obj(isline)]; + pobj = [Lf.legobj(ispatch) htmp.obj(ispatch)]; + + if ~isempty(hgidx) + for ih = hgidx + chldln1 = findall(Lf.legobj(ih), 'type', 'line'); + chldln2 = findall(htmp.obj(ih), 'type', 'line'); + + lobj = [lobj; [chldln1 chldln2]]; + + chldpa1 = findall(Lf.legobj(ih), 'type', 'patch'); + chldpa2 = findall(htmp.obj(ih), 'type', 'patch'); + + pobj = [pobj; [chldpa1 chldpa2]]; + + end + end + + lprops = {'color','linestyle','linewidth','marker','markersize','markeredgecolor','markerfacecolor'}; + for il = 1:size(lobj,1) + lvals = get(lobj(il,2), lprops); + pv = [lprops; lvals]; + set(lobj(il,1), pv{:}); + end + + pprops = {'cdata','cdatamapping','edgealpha','edgecolor','facealpha','facecolor','linestyle','linewidth','marker','markeredgecolor','markerfacecolor','markersize'}; + for ip = 1:size(pobj,1) + pvals = get(pobj(ip,2), pprops); + pv = [pprops; pvals]; + set(pobj(ip,1), pv{:}); + end + + cmap = colormap(htmp.leg); + colormap(legax, cmap); + + delete(htmp.leg); +end + +% Wrapper to add multiple callback functions to resize + +function wrapper(ObjH, EventData, fcnList) +for ii = 1:length(fcnList) + feval(fcnList{ii}, ObjH, EventData); +end + + + + + + + + diff --git a/Util/parseAttributeValue.m b/Util/parseAttributeValue.m index 93da212c1..7716dd6a0 100644 --- a/Util/parseAttributeValue.m +++ b/Util/parseAttributeValue.m @@ -163,7 +163,9 @@ % if there is no deployment database, set the value to an empty matrix ddb = readProperty('toolbox.ddb'); - if isempty(ddb), return; end + driver = readProperty('toolbox.ddb.driver'); + connection = readProperty('toolbox.ddb.connection'); + if isempty(ddb) && (isempty(driver) || isempty(connection)), return; end % get the relevant deployment/CTD cast if isfield(sample_data.meta, 'profile') @@ -206,7 +208,11 @@ % a foreign key, so our only choice is to give up if isempty(field_value), return; end - result = executeDDBQuery(related_table, related_pkey, field_value); + if strcmp('csv',ddb) + result = executeCSVQuery(related_table, related_pkey, field_value); + else + result = executeDDBQuery(related_table, related_pkey, field_value); + end if length(result) ~= 1, return; end value = result.(related_field); diff --git a/Util/parula.m b/Util/parula.m new file mode 100644 index 000000000..9bc34d417 --- /dev/null +++ b/Util/parula.m @@ -0,0 +1,77 @@ +function cm_data=parula(m) + +cm = [[0.2081, 0.1663, 0.5292], + [0.2116238095, 0.1897809524, 0.5776761905], + [0.212252381, 0.2137714286, 0.6269714286], + [0.2081, 0.2386, 0.6770857143], + [0.1959047619, 0.2644571429, 0.7279], + [0.1707285714, 0.2919380952, 0.779247619], + [0.1252714286, 0.3242428571, 0.8302714286], + [0.0591333333, 0.3598333333, 0.8683333333], + [0.0116952381, 0.3875095238, 0.8819571429], + [0.0059571429, 0.4086142857, 0.8828428571], + [0.0165142857, 0.4266, 0.8786333333], + [0.032852381, 0.4430428571, 0.8719571429], + [0.0498142857, 0.4585714286, 0.8640571429], + [0.0629333333, 0.4736904762, 0.8554380952], + [0.0722666667, 0.4886666667, 0.8467], + [0.0779428571, 0.5039857143, 0.8383714286], + [0.079347619, 0.5200238095, 0.8311809524], + [0.0749428571, 0.5375428571, 0.8262714286], + [0.0640571429, 0.5569857143, 0.8239571429], + [0.0487714286, 0.5772238095, 0.8228285714], + [0.0343428571, 0.5965809524, 0.819852381], + [0.0265, 0.6137, 0.8135], + [0.0238904762, 0.6286619048, 0.8037619048], + [0.0230904762, 0.6417857143, 0.7912666667], + [0.0227714286, 0.6534857143, 0.7767571429], + [0.0266619048, 0.6641952381, 0.7607190476], + [0.0383714286, 0.6742714286, 0.743552381], + [0.0589714286, 0.6837571429, 0.7253857143], + [0.0843, 0.6928333333, 0.7061666667], + [0.1132952381, 0.7015, 0.6858571429], + [0.1452714286, 0.7097571429, 0.6646285714], + [0.1801333333, 0.7176571429, 0.6424333333], + [0.2178285714, 0.7250428571, 0.6192619048], + [0.2586428571, 0.7317142857, 0.5954285714], + [0.3021714286, 0.7376047619, 0.5711857143], + [0.3481666667, 0.7424333333, 0.5472666667], + [0.3952571429, 0.7459, 0.5244428571], + [0.4420095238, 0.7480809524, 0.5033142857], + [0.4871238095, 0.7490619048, 0.4839761905], + [0.5300285714, 0.7491142857, 0.4661142857], + [0.5708571429, 0.7485190476, 0.4493904762], + [0.609852381, 0.7473142857, 0.4336857143], + [0.6473, 0.7456, 0.4188], + [0.6834190476, 0.7434761905, 0.4044333333], + [0.7184095238, 0.7411333333, 0.3904761905], + [0.7524857143, 0.7384, 0.3768142857], + [0.7858428571, 0.7355666667, 0.3632714286], + [0.8185047619, 0.7327333333, 0.3497904762], + [0.8506571429, 0.7299, 0.3360285714], + [0.8824333333, 0.7274333333, 0.3217], + [0.9139333333, 0.7257857143, 0.3062761905], + [0.9449571429, 0.7261142857, 0.2886428571], + [0.9738952381, 0.7313952381, 0.266647619], + [0.9937714286, 0.7454571429, 0.240347619], + [0.9990428571, 0.7653142857, 0.2164142857], + [0.9955333333, 0.7860571429, 0.196652381], + [0.988, 0.8066, 0.1793666667], + [0.9788571429, 0.8271428571, 0.1633142857], + [0.9697, 0.8481380952, 0.147452381], + [0.9625857143, 0.8705142857, 0.1309], + [0.9588714286, 0.8949, 0.1132428571], + [0.9598238095, 0.9218333333, 0.0948380952], + [0.9661, 0.9514428571, 0.0755333333], + [0.9763, 0.9831, 0.0538]]; + + if nargin < 1 + cm_data = cm; +else + cm_data = zeros(m,3); + hsv=rgb2hsv(cm); + cm_data(:,1)=interp1(linspace(0,1,size(cm,1)),hsv(:,1),linspace(0,1,m)); + cm_data(:,2)=interp1(linspace(0,1,size(cm,1)),hsv(:,2),linspace(0,1,m)); + cm_data(:,3)=interp1(linspace(0,1,size(cm,1)),hsv(:,3),linspace(0,1,m)); + cm_data=hsv2rgb(cm_data); +end \ No newline at end of file diff --git a/Util/plotclr.m b/Util/plotclr.m index f5dddcde3..ef503137f 100644 --- a/Util/plotclr.m +++ b/Util/plotclr.m @@ -1,4 +1,4 @@ -function h = plotclr(hAx, x, y, v, marker, vlim) +function h = plotclr(hAx, x, y, v, marker, vlim, varargin) % plots the values of v colour coded % at the positions specified by x and y. % A colourbar is added on the right side of the figure. @@ -79,10 +79,9 @@ else iv = (v > miv+(nc-1)*clrstep) & (v <= miv+nc*clrstep); end - htmp = plot(hAx, x(iv), y(iv), marker, ... 'Color', map(nc,:), ... - 'MarkerSize', sqrt(5)); + 'MarkerSize', sqrt(5), varargin{:}); if ~isempty(htmp), h = htmp; end @@ -94,10 +93,9 @@ else iv = (v > miv+(nc-1)*clrstep) & (v <= miv+nc*clrstep); end - htmp = plot(hAx, x(iv), y(iv), marker, ... 'Color', map(nc,:), ... - 'MarkerSize', sqrt(5)); + 'MarkerSize', sqrt(5), varargin{:}); if ~isempty(htmp), h = htmp; end end diff --git a/Util/select_points.m b/Util/select_points.m new file mode 100644 index 000000000..baa61c2cc --- /dev/null +++ b/Util/select_points.m @@ -0,0 +1,20 @@ +function [x,y] = select_points(hAx) +%function [x,y] = select_points +% Uses rbbox to select points in the timeseries chart for flagging. +% Returns [x,y] - index of rectangle corners in figure units +axes(hAx); +k = waitforbuttonpress; +point1 = get(gca,'CurrentPoint'); % button down detected +finalRect = rbbox; % return figure units +point2 = get(gca,'CurrentPoint'); % button up detected +point1 = point1(1,1:2); % extract x and y +point2 = point2(1,1:2); +p1 = min(point1,point2); % calculate locations +offset = abs(point1-point2); % and dimensions +x = [p1(1) p1(1)+offset(1) p1(1)+offset(1) p1(1) p1(1)]; +y = [p1(2) p1(2) p1(2)+offset(2) p1(2)+offset(2) p1(2)]; +hold on +axis manual +plot(x,y); % redraw in dataspace units + +end \ No newline at end of file diff --git a/Util/setpos.m b/Util/setpos.m new file mode 100644 index 000000000..c474036f3 --- /dev/null +++ b/Util/setpos.m @@ -0,0 +1,187 @@ +function setpos(h,fmt,href) +% SETPOS Set graphics object position in a flexible way. +% SETPOS(H,FMT) sets the position property of graphics object +% with handle H, according to FMT that can be expressed using different +% units. H must have a "Position' property. +% +% FMT is a char array containing 4 strings separated by colon or space. +% The format of each string is one of "%1c%f%2c" or "%1c%d%2c" where the +% first optional argument is "+" or "-", the second one is a number and +% the last one is two characters that specify the unit as : +% +% px for Pixels +% nz for Normalized +% in for Inches +% cm for Centimeters +% pt for Points +% ch for Characters +% [] (empty) for Current units [See get(H,'units')] +% +% For better rendering, SETPOS can be included into the "Createfcn" or +% "Resizefcn" properties of the graphical object H. +% +% Any string value of FMT can be replaced by a single '#' to keep the current +% value of the corresponding parameter. +% +% The first optional argument of FMT is used to increase ('+') or +% decrease ('-') the corresponding value. +% +% Note that SETPOS(H,FMT) works as set(H,'Position',FMT) when FMT is +% a 4 double values vector. +% +% SETPOS(H,FMT,HREF) sets the position of the graphics object H according to +% FMT, but using the position of the graphics object HREF as reference instead +% of the parent of H. HREF must be a valid handle and must have a "Position" +% property (except for the Root object). Note that this should only affect +% Left&Bottom (1st&2nd) element of the "Position" vector of H. +% +% See also GETPOS, SET, GET. + +% Author: Jérôme Briot, Matlab 6.1.0.450 (R12.1) +% Contact: dutmatlab@yahoo.fr +% Revision: 1.0 (12-Feb-2007) +% 1.1 (14-Feb-2007) Third input argument HREF added. +% Minor corrections in the help section. +% 1.2 (21-Feb-2007) Bug fixed if HREF is the Root object +% Examples removed from the help section +% Comments: +% + +% Check the number of input arguments +error(nargchk(2,3, nargin)); + +% Check if H is a graphics object handle +if ~ishandle(h) + error('First argument must be a graphic object handle in SETPOS(H,FMT)'); +end + +% If FMT is a 4x1 double vector then SETPOS works as SET(H,'Position',FMT) +if isnumeric(fmt) & numel(fmt(:))==4 + + set(h,'position',fmt) + return + +% If FMT is not a double vector, check if it's a char string +elseif ~ischar(fmt) + + error('FMT argument must be a string or a 4 elements vector in SETPOS(H,FMT)'); + +end + +if nargin==2 % SETPOS(H,FMT) + + %HREF = parent of H + href=get(h,'parent'); + +elseif nargin==3 % SETPOS(H,FMT,HREF) + + if ~ishandle(href) % Check if HREF is a valid handle + error('HREF must be a valid handle of a graphics object in SETPOS(H,FMT,HREF)') + end + +end + +flag_href=0; +% Don't use HREF position if it is the parent of H +if href~=get(h,'parent') + flag_href=1; +end + +% Extract 4 char strings from FMT +M=strread(fmt,'%s','delimiter',' ,','emptyvalue',0); + +% Store the current unit of the graphics object H +current_unit=get(h,'units'); +% Store the current unit of the reference object HREF +current_ref_unit=get(href,'units'); + +% List available units +available_units={'inches' 'centimeters' 'normalized' 'points' 'pixels' 'characters'}; + +flag=zeros(1,4); + +% Decode elements of FMT +for n=1:numel(M) + + % If FMT(n) is not a "#" + if ~strcmp(M{n},'#') + + % Check if FMT(n) is +%... or -%... + if strncmp(M{n},'+',1) + flag(n)=1; + M{n}(1)=[]; % Remove '+' char + elseif strncmp(M{n},'-',1) + flag(n)=-1; + M{n}(1)=[]; % Remove '-' char + end + + % Separate value and unit from FMT(n) + [val(n),temp_unit]=strread(M{n},'%f%s'); + + % If the unit is not specified in FMT(n) + if isempty(temp_unit) + + unit{n}=current_unit; % Set the units to the current one + + % Else check if the units paramter is valid + else idx=strcmpi(temp_unit,{'in' 'cm' 'nz' 'pt' 'px' 'ch'}); + + if ~any(idx) + error('Units must be one of "in", "cm", "nz", "pt", "px" or "ch"') + end + + unit{n}=available_units{idx}; % Set the units to one from the list + + end + + end + +end + +% Set position of H using decoded FMT +for n=1:numel(M) + + % If FMT(n) is not a "#" => value to modify + if ~strcmp(M{n},'#') + + % Modify the "Units" property of H + set(h,'units',unit{n}); + % Modify the "Units" property of HREF + set(href,'units',unit{n}); + % Get the current "Position" vector of H + position_in_unit=get(h,'position'); + % Get the current "Position" vector of HREF + if (isnumeric(href) && ~href) || (isgraphics(href) && isequal(href, groot)) % HREF is the Root object (no 'Position' property) + position_ref_unit=get(href,'screensize'); %%% Should be safe here ! + else position_ref_unit=get(href,'position'); + end + if ~flag % No "+" or "-" + + if any(n==[1 2]) + % If HREF is specified and is not the parent of H, flag_href=1 else flag_href=0 + position_in_unit(n)=val(n)+position_ref_unit(n)*flag_href; + else position_in_unit(n)=val(n); + end + + elseif any(n==[3 4]) % "+" or "-" and FMT(n) is width or height + + position_in_unit(n)=position_in_unit(n)+val(n)*flag(n); + + else % "+" or "-" and FMT(n) is left or bottom + + position_in_unit(n)=position_in_unit(n)+val(n)*flag(n); + position_in_unit(n+2)=position_in_unit(n+2)-val(n)*flag(n); + + end + + % Modify the "Position" property of H + set(h,'position',position_in_unit) + + end + +end + +% Restore the unit of the graphics object H +set(h,'units',current_unit); +% Restore the unit of the reference object HREF +set(href,'units',current_ref_unit); \ No newline at end of file diff --git a/Util/viridis.m b/Util/viridis.m new file mode 100644 index 000000000..f48e56bf2 --- /dev/null +++ b/Util/viridis.m @@ -0,0 +1,269 @@ +% http://www.mathworks.com/matlabcentral/fileexchange/51986-perceptually-uniform-colormaps/content/Colormaps/viridis.m +function cm_data=viridis(m) +cm = [[ 0.26700401, 0.00487433, 0.32941519], + [ 0.26851048, 0.00960483, 0.33542652], + [ 0.26994384, 0.01462494, 0.34137895], + [ 0.27130489, 0.01994186, 0.34726862], + [ 0.27259384, 0.02556309, 0.35309303], + [ 0.27380934, 0.03149748, 0.35885256], + [ 0.27495242, 0.03775181, 0.36454323], + [ 0.27602238, 0.04416723, 0.37016418], + [ 0.2770184 , 0.05034437, 0.37571452], + [ 0.27794143, 0.05632444, 0.38119074], + [ 0.27879067, 0.06214536, 0.38659204], + [ 0.2795655 , 0.06783587, 0.39191723], + [ 0.28026658, 0.07341724, 0.39716349], + [ 0.28089358, 0.07890703, 0.40232944], + [ 0.28144581, 0.0843197 , 0.40741404], + [ 0.28192358, 0.08966622, 0.41241521], + [ 0.28232739, 0.09495545, 0.41733086], + [ 0.28265633, 0.10019576, 0.42216032], + [ 0.28291049, 0.10539345, 0.42690202], + [ 0.28309095, 0.11055307, 0.43155375], + [ 0.28319704, 0.11567966, 0.43611482], + [ 0.28322882, 0.12077701, 0.44058404], + [ 0.28318684, 0.12584799, 0.44496 ], + [ 0.283072 , 0.13089477, 0.44924127], + [ 0.28288389, 0.13592005, 0.45342734], + [ 0.28262297, 0.14092556, 0.45751726], + [ 0.28229037, 0.14591233, 0.46150995], + [ 0.28188676, 0.15088147, 0.46540474], + [ 0.28141228, 0.15583425, 0.46920128], + [ 0.28086773, 0.16077132, 0.47289909], + [ 0.28025468, 0.16569272, 0.47649762], + [ 0.27957399, 0.17059884, 0.47999675], + [ 0.27882618, 0.1754902 , 0.48339654], + [ 0.27801236, 0.18036684, 0.48669702], + [ 0.27713437, 0.18522836, 0.48989831], + [ 0.27619376, 0.19007447, 0.49300074], + [ 0.27519116, 0.1949054 , 0.49600488], + [ 0.27412802, 0.19972086, 0.49891131], + [ 0.27300596, 0.20452049, 0.50172076], + [ 0.27182812, 0.20930306, 0.50443413], + [ 0.27059473, 0.21406899, 0.50705243], + [ 0.26930756, 0.21881782, 0.50957678], + [ 0.26796846, 0.22354911, 0.5120084 ], + [ 0.26657984, 0.2282621 , 0.5143487 ], + [ 0.2651445 , 0.23295593, 0.5165993 ], + [ 0.2636632 , 0.23763078, 0.51876163], + [ 0.26213801, 0.24228619, 0.52083736], + [ 0.26057103, 0.2469217 , 0.52282822], + [ 0.25896451, 0.25153685, 0.52473609], + [ 0.25732244, 0.2561304 , 0.52656332], + [ 0.25564519, 0.26070284, 0.52831152], + [ 0.25393498, 0.26525384, 0.52998273], + [ 0.25219404, 0.26978306, 0.53157905], + [ 0.25042462, 0.27429024, 0.53310261], + [ 0.24862899, 0.27877509, 0.53455561], + [ 0.2468114 , 0.28323662, 0.53594093], + [ 0.24497208, 0.28767547, 0.53726018], + [ 0.24311324, 0.29209154, 0.53851561], + [ 0.24123708, 0.29648471, 0.53970946], + [ 0.23934575, 0.30085494, 0.54084398], + [ 0.23744138, 0.30520222, 0.5419214 ], + [ 0.23552606, 0.30952657, 0.54294396], + [ 0.23360277, 0.31382773, 0.54391424], + [ 0.2316735 , 0.3181058 , 0.54483444], + [ 0.22973926, 0.32236127, 0.54570633], + [ 0.22780192, 0.32659432, 0.546532 ], + [ 0.2258633 , 0.33080515, 0.54731353], + [ 0.22392515, 0.334994 , 0.54805291], + [ 0.22198915, 0.33916114, 0.54875211], + [ 0.22005691, 0.34330688, 0.54941304], + [ 0.21812995, 0.34743154, 0.55003755], + [ 0.21620971, 0.35153548, 0.55062743], + [ 0.21429757, 0.35561907, 0.5511844 ], + [ 0.21239477, 0.35968273, 0.55171011], + [ 0.2105031 , 0.36372671, 0.55220646], + [ 0.20862342, 0.36775151, 0.55267486], + [ 0.20675628, 0.37175775, 0.55311653], + [ 0.20490257, 0.37574589, 0.55353282], + [ 0.20306309, 0.37971644, 0.55392505], + [ 0.20123854, 0.38366989, 0.55429441], + [ 0.1994295 , 0.38760678, 0.55464205], + [ 0.1976365 , 0.39152762, 0.55496905], + [ 0.19585993, 0.39543297, 0.55527637], + [ 0.19410009, 0.39932336, 0.55556494], + [ 0.19235719, 0.40319934, 0.55583559], + [ 0.19063135, 0.40706148, 0.55608907], + [ 0.18892259, 0.41091033, 0.55632606], + [ 0.18723083, 0.41474645, 0.55654717], + [ 0.18555593, 0.4185704 , 0.55675292], + [ 0.18389763, 0.42238275, 0.55694377], + [ 0.18225561, 0.42618405, 0.5571201 ], + [ 0.18062949, 0.42997486, 0.55728221], + [ 0.17901879, 0.43375572, 0.55743035], + [ 0.17742298, 0.4375272 , 0.55756466], + [ 0.17584148, 0.44128981, 0.55768526], + [ 0.17427363, 0.4450441 , 0.55779216], + [ 0.17271876, 0.4487906 , 0.55788532], + [ 0.17117615, 0.4525298 , 0.55796464], + [ 0.16964573, 0.45626209, 0.55803034], + [ 0.16812641, 0.45998802, 0.55808199], + [ 0.1666171 , 0.46370813, 0.55811913], + [ 0.16511703, 0.4674229 , 0.55814141], + [ 0.16362543, 0.47113278, 0.55814842], + [ 0.16214155, 0.47483821, 0.55813967], + [ 0.16066467, 0.47853961, 0.55811466], + [ 0.15919413, 0.4822374 , 0.5580728 ], + [ 0.15772933, 0.48593197, 0.55801347], + [ 0.15626973, 0.4896237 , 0.557936 ], + [ 0.15481488, 0.49331293, 0.55783967], + [ 0.15336445, 0.49700003, 0.55772371], + [ 0.1519182 , 0.50068529, 0.55758733], + [ 0.15047605, 0.50436904, 0.55742968], + [ 0.14903918, 0.50805136, 0.5572505 ], + [ 0.14760731, 0.51173263, 0.55704861], + [ 0.14618026, 0.51541316, 0.55682271], + [ 0.14475863, 0.51909319, 0.55657181], + [ 0.14334327, 0.52277292, 0.55629491], + [ 0.14193527, 0.52645254, 0.55599097], + [ 0.14053599, 0.53013219, 0.55565893], + [ 0.13914708, 0.53381201, 0.55529773], + [ 0.13777048, 0.53749213, 0.55490625], + [ 0.1364085 , 0.54117264, 0.55448339], + [ 0.13506561, 0.54485335, 0.55402906], + [ 0.13374299, 0.54853458, 0.55354108], + [ 0.13244401, 0.55221637, 0.55301828], + [ 0.13117249, 0.55589872, 0.55245948], + [ 0.1299327 , 0.55958162, 0.55186354], + [ 0.12872938, 0.56326503, 0.55122927], + [ 0.12756771, 0.56694891, 0.55055551], + [ 0.12645338, 0.57063316, 0.5498411 ], + [ 0.12539383, 0.57431754, 0.54908564], + [ 0.12439474, 0.57800205, 0.5482874 ], + [ 0.12346281, 0.58168661, 0.54744498], + [ 0.12260562, 0.58537105, 0.54655722], + [ 0.12183122, 0.58905521, 0.54562298], + [ 0.12114807, 0.59273889, 0.54464114], + [ 0.12056501, 0.59642187, 0.54361058], + [ 0.12009154, 0.60010387, 0.54253043], + [ 0.11973756, 0.60378459, 0.54139999], + [ 0.11951163, 0.60746388, 0.54021751], + [ 0.11942341, 0.61114146, 0.53898192], + [ 0.11948255, 0.61481702, 0.53769219], + [ 0.11969858, 0.61849025, 0.53634733], + [ 0.12008079, 0.62216081, 0.53494633], + [ 0.12063824, 0.62582833, 0.53348834], + [ 0.12137972, 0.62949242, 0.53197275], + [ 0.12231244, 0.63315277, 0.53039808], + [ 0.12344358, 0.63680899, 0.52876343], + [ 0.12477953, 0.64046069, 0.52706792], + [ 0.12632581, 0.64410744, 0.52531069], + [ 0.12808703, 0.64774881, 0.52349092], + [ 0.13006688, 0.65138436, 0.52160791], + [ 0.13226797, 0.65501363, 0.51966086], + [ 0.13469183, 0.65863619, 0.5176488 ], + [ 0.13733921, 0.66225157, 0.51557101], + [ 0.14020991, 0.66585927, 0.5134268 ], + [ 0.14330291, 0.66945881, 0.51121549], + [ 0.1466164 , 0.67304968, 0.50893644], + [ 0.15014782, 0.67663139, 0.5065889 ], + [ 0.15389405, 0.68020343, 0.50417217], + [ 0.15785146, 0.68376525, 0.50168574], + [ 0.16201598, 0.68731632, 0.49912906], + [ 0.1663832 , 0.69085611, 0.49650163], + [ 0.1709484 , 0.69438405, 0.49380294], + [ 0.17570671, 0.6978996 , 0.49103252], + [ 0.18065314, 0.70140222, 0.48818938], + [ 0.18578266, 0.70489133, 0.48527326], + [ 0.19109018, 0.70836635, 0.48228395], + [ 0.19657063, 0.71182668, 0.47922108], + [ 0.20221902, 0.71527175, 0.47608431], + [ 0.20803045, 0.71870095, 0.4728733 ], + [ 0.21400015, 0.72211371, 0.46958774], + [ 0.22012381, 0.72550945, 0.46622638], + [ 0.2263969 , 0.72888753, 0.46278934], + [ 0.23281498, 0.73224735, 0.45927675], + [ 0.2393739 , 0.73558828, 0.45568838], + [ 0.24606968, 0.73890972, 0.45202405], + [ 0.25289851, 0.74221104, 0.44828355], + [ 0.25985676, 0.74549162, 0.44446673], + [ 0.26694127, 0.74875084, 0.44057284], + [ 0.27414922, 0.75198807, 0.4366009 ], + [ 0.28147681, 0.75520266, 0.43255207], + [ 0.28892102, 0.75839399, 0.42842626], + [ 0.29647899, 0.76156142, 0.42422341], + [ 0.30414796, 0.76470433, 0.41994346], + [ 0.31192534, 0.76782207, 0.41558638], + [ 0.3198086 , 0.77091403, 0.41115215], + [ 0.3277958 , 0.77397953, 0.40664011], + [ 0.33588539, 0.7770179 , 0.40204917], + [ 0.34407411, 0.78002855, 0.39738103], + [ 0.35235985, 0.78301086, 0.39263579], + [ 0.36074053, 0.78596419, 0.38781353], + [ 0.3692142 , 0.78888793, 0.38291438], + [ 0.37777892, 0.79178146, 0.3779385 ], + [ 0.38643282, 0.79464415, 0.37288606], + [ 0.39517408, 0.79747541, 0.36775726], + [ 0.40400101, 0.80027461, 0.36255223], + [ 0.4129135 , 0.80304099, 0.35726893], + [ 0.42190813, 0.80577412, 0.35191009], + [ 0.43098317, 0.80847343, 0.34647607], + [ 0.44013691, 0.81113836, 0.3409673 ], + [ 0.44936763, 0.81376835, 0.33538426], + [ 0.45867362, 0.81636288, 0.32972749], + [ 0.46805314, 0.81892143, 0.32399761], + [ 0.47750446, 0.82144351, 0.31819529], + [ 0.4870258 , 0.82392862, 0.31232133], + [ 0.49661536, 0.82637633, 0.30637661], + [ 0.5062713 , 0.82878621, 0.30036211], + [ 0.51599182, 0.83115784, 0.29427888], + [ 0.52577622, 0.83349064, 0.2881265 ], + [ 0.5356211 , 0.83578452, 0.28190832], + [ 0.5455244 , 0.83803918, 0.27562602], + [ 0.55548397, 0.84025437, 0.26928147], + [ 0.5654976 , 0.8424299 , 0.26287683], + [ 0.57556297, 0.84456561, 0.25641457], + [ 0.58567772, 0.84666139, 0.24989748], + [ 0.59583934, 0.84871722, 0.24332878], + [ 0.60604528, 0.8507331 , 0.23671214], + [ 0.61629283, 0.85270912, 0.23005179], + [ 0.62657923, 0.85464543, 0.22335258], + [ 0.63690157, 0.85654226, 0.21662012], + [ 0.64725685, 0.85839991, 0.20986086], + [ 0.65764197, 0.86021878, 0.20308229], + [ 0.66805369, 0.86199932, 0.19629307], + [ 0.67848868, 0.86374211, 0.18950326], + [ 0.68894351, 0.86544779, 0.18272455], + [ 0.69941463, 0.86711711, 0.17597055], + [ 0.70989842, 0.86875092, 0.16925712], + [ 0.72039115, 0.87035015, 0.16260273], + [ 0.73088902, 0.87191584, 0.15602894], + [ 0.74138803, 0.87344918, 0.14956101], + [ 0.75188414, 0.87495143, 0.14322828], + [ 0.76237342, 0.87642392, 0.13706449], + [ 0.77285183, 0.87786808, 0.13110864], + [ 0.78331535, 0.87928545, 0.12540538], + [ 0.79375994, 0.88067763, 0.12000532], + [ 0.80418159, 0.88204632, 0.11496505], + [ 0.81457634, 0.88339329, 0.11034678], + [ 0.82494028, 0.88472036, 0.10621724], + [ 0.83526959, 0.88602943, 0.1026459 ], + [ 0.84556056, 0.88732243, 0.09970219], + [ 0.8558096 , 0.88860134, 0.09745186], + [ 0.86601325, 0.88986815, 0.09595277], + [ 0.87616824, 0.89112487, 0.09525046], + [ 0.88627146, 0.89237353, 0.09537439], + [ 0.89632002, 0.89361614, 0.09633538], + [ 0.90631121, 0.89485467, 0.09812496], + [ 0.91624212, 0.89609127, 0.1007168 ], + [ 0.92610579, 0.89732977, 0.10407067], + [ 0.93590444, 0.8985704 , 0.10813094], + [ 0.94563626, 0.899815 , 0.11283773], + [ 0.95529972, 0.90106534, 0.11812832], + [ 0.96489353, 0.90232311, 0.12394051], + [ 0.97441665, 0.90358991, 0.13021494], + [ 0.98386829, 0.90486726, 0.13689671], + [ 0.99324789, 0.90615657, 0.1439362 ]]; + +if nargin < 1 + cm_data = cm; +else + cm_data = zeros(m,3); + hsv=rgb2hsv(cm); + cm_data=interp1(linspace(0,1,size(cm,1)),hsv,linspace(0,1,m)); + cm_data=hsv2rgb(cm_data); + +end +end \ No newline at end of file diff --git a/imosToolbox.m b/imosToolbox.m index ea266c816..728e71964 100644 --- a/imosToolbox.m +++ b/imosToolbox.m @@ -67,13 +67,13 @@ function imosToolbox(auto, varargin) % we must dynamically add the ddb.jar java library to the classpath % as well as any other .jar library and jdbc drivers -jars = fsearch('.jar', fullfile(path, 'Java'), 'files'); +jars = fsearchRegexp('.jar$', fullfile(path, 'Java'), 'files'); for j = 1 : length(jars) javaaddpath(jars{j}); end % Set current toolbox version -toolboxVersion = ['2.5.4 - ' computer]; +toolboxVersion = ['2.5.5 - ' computer]; switch auto case 'auto', autoIMOSToolbox(toolboxVersion, varargin{:}); diff --git a/imosToolbox_Linux64.bin b/imosToolbox_Linux64.bin index db417653e..991045dc3 100755 Binary files a/imosToolbox_Linux64.bin and b/imosToolbox_Linux64.bin differ diff --git a/imosToolbox_Win32.exe b/imosToolbox_Win32.exe index 4c5ce6bac..560ca33cc 100644 Binary files a/imosToolbox_Win32.exe and b/imosToolbox_Win32.exe differ diff --git a/imosToolbox_Win64.exe b/imosToolbox_Win64.exe index 92eca9e38..d2199ea30 100644 Binary files a/imosToolbox_Win64.exe and b/imosToolbox_Win64.exe differ diff --git a/toolboxProperties.txt b/toolboxProperties.txt index 2b2b03675..bf710d5df 100644 --- a/toolboxProperties.txt +++ b/toolboxProperties.txt @@ -91,3 +91,12 @@ saveGraph.imgType = png % visual QC plots export properties visualQC.export = true visualQC.fastScatter = true + +% default nonspecialized scalar colormap and number of colour steps +% jet | parula | viridis +visualQC.defaultColormap = parula +visualQC.ncolors = 64 + +% simple zbuffering of markers if using fallback fastScatterMesh plot (fastScatter = false) +% 'ascending', 'descending', 'triangle', 'vee', 'flat', 'parabolic', 'hamming', 'hann' +visualQC.zbuffer = triangle \ No newline at end of file