diff --git a/GUI/mainWindow.m b/GUI/mainWindow.m index 4edaee5f4..022e33ba7 100644 --- a/GUI/mainWindow.m +++ b/GUI/mainWindow.m @@ -254,8 +254,8 @@ function mainWindow(... buttons = findall(tb); zoomoutb = findobj(buttons, 'TooltipString', 'Zoom Out'); - zoominb = findobj(buttons, 'TooltipString', 'Zoom In (Ctrl+z)'); - panb = findobj(buttons, 'TooltipString', 'Pan (Ctrl+a)'); + zoominb = findobj(buttons, 'TooltipString', 'Zoom In'); + panb = findobj(buttons, 'TooltipString', 'Pan'); datacursorb = findobj(buttons, 'TooltipString', 'Data Cursor'); buttons(buttons == tb) = []; @@ -273,6 +273,10 @@ function mainWindow(... set(hZoom, 'ActionPostCallback', @zoomPostCallback); set(hPan, 'ActionPostCallback', @zoomPostCallback); + % update zoom in and pan's tooltips to display hot keys + set(zoominb, 'TooltipString', 'Zoom In (Ctrl+z)'); + set(panb, 'TooltipString', 'Pan (Ctrl+a)'); + %set uimenu hToolsMenu = uimenu(fig, 'label', 'Tools'); switch mode diff --git a/Graph/checkMooringPlannedDepths.m b/Graph/checkMooringPlannedDepths.m index df83faddd..f7e80d053 100644 --- a/Graph/checkMooringPlannedDepths.m +++ b/Graph/checkMooringPlannedDepths.m @@ -277,7 +277,8 @@ function checkMooringPlannedDepths(sample_data, isQC, saveToFile, exportDir) 'buffer', [0 -hYBuffer], ... 'ncol', nCols,... 'FontSize', fontSizeAx,... - 'xscale',xscale); + 'xscale', xscale, ... + 'parent', hPanelMooringVar); posAx = get(hAxPress, 'Position'); set(hLegend, 'Units', 'Normalized', 'color', backgroundColor); diff --git a/Graph/checkMooringPresDiffs.m b/Graph/checkMooringPresDiffs.m index 802d0dc57..4f56e08bd 100644 --- a/Graph/checkMooringPresDiffs.m +++ b/Graph/checkMooringPresDiffs.m @@ -322,7 +322,8 @@ function checkMooringPresDiffs(sample_data, iSampleMenu, isQC, saveToFile, expor 'buffer', [0 -hYBuffer], ... 'ncol', nCols,... 'FontSize', fontSizeAx,... - 'xscale',xscale); + 'xscale', xscale, ... + 'parent', hPanelMooringVar); posAx = get(hAxPress, 'Position'); set(hLegend, 'Units', 'Normalized', 'color', backgroundColor); diff --git a/Graph/lineMooring1DVar.m b/Graph/lineMooring1DVar.m index e9f8f0015..c1d1f7f62 100644 --- a/Graph/lineMooring1DVar.m +++ b/Graph/lineMooring1DVar.m @@ -278,7 +278,8 @@ function lineMooring1DVar(sample_data, varName, isQC, saveToFile, exportDir) 'buffer', [0 -hYBuffer], ... 'ncol', nCols,... 'FontSize', fontSizeAx,... - 'xscale',xscale); + 'xscale', xscale, ... + 'parent', hPanelMooringVar); posAx = get(hAxMooringVar, 'Position'); set(hLegend, 'Units', 'Normalized', 'color', backgroundColor); posLh = get(hLegend, 'Position'); diff --git a/Graph/scatterMooring1DVarAgainstDepth.m b/Graph/scatterMooring1DVarAgainstDepth.m index 9337545e6..48a51a697 100644 --- a/Graph/scatterMooring1DVarAgainstDepth.m +++ b/Graph/scatterMooring1DVarAgainstDepth.m @@ -383,7 +383,8 @@ function scatterMooring1DVarAgainstDepth(sample_data, varName, isQC, saveToFile, 'buffer', [0 -hYBuffer], ... 'ncol', nCols,... 'FontSize', fontSizeAx, ... - 'xscale', xscale); + 'xscale', xscale, ... + 'parent', hPanelMooringVar); entries = get(hLegend,'children'); % if used mesh for scatter plot then have to clean up legend % entries diff --git a/Graph/scatterMooring2DVarAgainstDepth.m b/Graph/scatterMooring2DVarAgainstDepth.m index 836ae4e2e..5f91d3f48 100644 --- a/Graph/scatterMooring2DVarAgainstDepth.m +++ b/Graph/scatterMooring2DVarAgainstDepth.m @@ -463,7 +463,9 @@ function scatterMooring2DVarAgainstDepth(sample_data, varName, isQC, saveToFile, 'anchor', [6 2], ... 'buffer', [0 -hYBuffer], ... 'ncol', nCols,... - 'FontSize', fontSizeAx,'xscale',xscale); + 'FontSize', fontSizeAx, ... + 'xscale',xscale, ... + 'parent', hPanelMooringVar); % if used mesh for scatter plot then have to clean up legend % entries diff --git a/IMOS/imosParameters.txt b/IMOS/imosParameters.txt index b2decbf82..7aedca5ac 100644 --- a/IMOS/imosParameters.txt +++ b/IMOS/imosParameters.txt @@ -1,6 +1,6 @@ % % A list of all IMOS compliant parameter names, associated standard names from version 20 -% (http://cf-pcmdi.llnl.gov/documents/cf-standard-names/standard-name-table/20/cf-standard-name-table.html/), +% (http://cfconventions.org/Data/cf-standard-names/20/build/cf-standard-name-table.html), % units of measurement, fill values, and valid min/max values. Entries are in the following format: % % parameter name, is cf parameter, standard/long name, units of measurement, direction positive, reference datum, data code, fillValue, validMin, validMax, NetCDF C type @@ -11,6 +11,7 @@ % of a comment. % +ABSI, 0, backscatter_intensity_from_acoustic_beam, decibel, , , A, 999999.0, 0.0, 150.0, float ABSI1, 0, backscatter_intensity_from_acoustic_beam_1, decibel, , , A, 999999.0, 0.0, 150.0, float ABSI2, 0, backscatter_intensity_from_acoustic_beam_2, decibel, , , A, 999999.0, 0.0, 150.0, float ABSI3, 0, backscatter_intensity_from_acoustic_beam_3, decibel, , , A, 999999.0, 0.0, 150.0, float @@ -42,6 +43,7 @@ CPHL, 0, mass_concentration_of_inferred_chlorophyll_from_relative CHLF, 0, mass_concentration_of_inferred_chlorophyll_from_relative_fluorescence_units_in_sea_water, mg m-3, , , K, 999999.0, 0.0, 100.0, float CHLU, 0, mass_concentration_of_inferred_chlorophyll_from_relative_fluorescence_units_in_sea_water, mg m-3, , , K, 999999.0, 0.0, 100.0, float CSPD, 1, sea_water_speed, m s-1, , , V, 999999.0, 0.0, 10.0, float +CSPD_STD, 0, sea_water_speed_standard_deviation, m s-1, , , V, 999999.0, 0.0, 10.0, float DENS, 1, sea_water_density, kg m-3, , , D, 999999.0, , , float DEPTH, 1, depth, m, down, sea surface, Z, 999999.0, -5.0, 12000.0, float DESC, 0, profiling_descent_rate_of_instrument, m s-1, , , E, 999999.0, 0.0, 5.0, float diff --git a/Parser/RCMParse.m b/Parser/RCMParse.m new file mode 100644 index 000000000..895325ec7 --- /dev/null +++ b/Parser/RCMParse.m @@ -0,0 +1,320 @@ +function sample_data = RCMParse( filename, mode ) +%RCMParse Parses a .txt data file from RCM-8 and old Seaguard RCM files processed with +%Aanderaa software. +% +% - processed header - header information. +% Typically first 2 lines. +% - data - Rows of tab seperated data. +% +% This function reads in the header sections, and delegates to the two file +% specific sub functions to process the data. +% +% Inputs: +% filename - cell array of files to import (only one supported). +% mode - Toolbox data type mode. +% +% Outputs: +% sample_data - Struct containing sample data. +% +% Code based on VemcoParse.m, itself base on readSBE37cnv.m +% +% Author: Peter Jansen + +% +% Copyright (c) 2016, Australian Ocean Data Network (AODN) 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 AODN/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(1,2); + +if ~iscellstr(filename) + error('filename must be a cell array of strings'); +end + +% only one file supported currently +filename = filename{1}; + +% read in every line of header in the file, then big read of data +procHeaderLines = {}; +try + % header is two lines + fid = fopen(filename, 'rt'); + line = fgetl(fid); + procHeaderLines{end+1} = line; + line = fgetl(fid); + procHeaderLines{end+1} = line; + dataHeaderLine=line; + + % assume date and time would always be the first and second column, if + % not will need to make a regexp for dataHeaderLine and get the index + iDate=2; + + iDateTimeCol=iDate; + ncolumns=numel(regexp(dataHeaderLine,'\t','split')); + + % consruct a format string using %s for date and time, and %f32 for + % everything else + formatstr = ''; + for k = 1:ncolumns + if ismember(k,iDateTimeCol) + formatstr = [formatstr '%s']; + else + formatstr = [formatstr '%f32']; + end + end + + dataLines = textscan(fid,formatstr,'Delimiter','\t'); + + fclose(fid); + +catch e + if fid ~= -1, fclose(fid); end + rethrow(e); +end + +% read in the raw instrument header +procHeader = parseProcessedHeader( procHeaderLines, dataHeaderLine); +procHeader.toolbox_input_file = filename; + +% use Vemco specific csv reader function +[data, comment] = readRCMtxt(dataLines, procHeader); + +% create sample data struct, +% and copy all the data in +sample_data = struct; + +sample_data.toolbox_input_file = filename; +sample_data.meta.featureType = mode; +sample_data.meta.procHeader = procHeader; + +sample_data.meta.instrument_make = 'Aanderaa'; +if isfield(procHeader, 'instrument_model') + sample_data.meta.instrument_model = procHeader.instrument_model; +else + sample_data.meta.instrument_model = 'Sea Guard'; +end + +if isfield(procHeader, 'instrument_firmware') + sample_data.meta.instrument_firmware = procHeader.instrument_firmware; +else + sample_data.meta.instrument_firmware = ''; +end + +if isfield(procHeader, 'instrument_serial_no') + sample_data.meta.instrument_serial_no = procHeader.instrument_serial_no; +else + sample_data.meta.instrument_serial_no = ''; +end + +time = data.TIME; + +if isfield(procHeader, 'sampleInterval') + sample_data.meta.instrument_sample_interval = procHeader.sampleInterval; +else + sample_data.meta.instrument_sample_interval = median(diff(time*24*3600)); +end + +sample_data.dimensions = {}; +sample_data.variables = {}; + +% generate time data from header information +sample_data.dimensions{1}.name = 'TIME'; +sample_data.dimensions{1}.typeCastFunc = str2func(netcdf3ToMatlabType(imosParameters(sample_data.dimensions{1}.name, 'type'))); +sample_data.dimensions{1}.data = sample_data.dimensions{1}.typeCastFunc(time); + +sample_data.variables{end+1}.name = 'TIMESERIES'; +sample_data.variables{end}.typeCastFunc = str2func(netcdf3ToMatlabType(imosParameters(sample_data.variables{end}.name, 'type'))); +sample_data.variables{end}.data = sample_data.variables{end}.typeCastFunc(1); +sample_data.variables{end}.dimensions = []; +sample_data.variables{end+1}.name = 'LATITUDE'; +sample_data.variables{end}.typeCastFunc = str2func(netcdf3ToMatlabType(imosParameters(sample_data.variables{end}.name, 'type'))); +sample_data.variables{end}.data = sample_data.variables{end}.typeCastFunc(NaN); +sample_data.variables{end}.dimensions = []; +sample_data.variables{end+1}.name = 'LONGITUDE'; +sample_data.variables{end}.typeCastFunc = str2func(netcdf3ToMatlabType(imosParameters(sample_data.variables{end}.name, 'type'))); +sample_data.variables{end}.data = sample_data.variables{end}.typeCastFunc(NaN); +sample_data.variables{end}.dimensions = []; +sample_data.variables{end+1}.name = 'NOMINAL_DEPTH'; +sample_data.variables{end}.typeCastFunc = str2func(netcdf3ToMatlabType(imosParameters(sample_data.variables{end}.name, 'type'))); +sample_data.variables{end}.data = sample_data.variables{end}.typeCastFunc(NaN); +sample_data.variables{end}.dimensions = []; + +% scan through the list of parameters that were read +% from the file, and create a variable for each +vars = fieldnames(data); +coordinates = 'TIME LATITUDE LONGITUDE NOMINAL_DEPTH'; +for k = 1:length(vars) + + if strncmp('TIME', vars{k}, 4), continue; end + + % dimensions definition must stay in this order : T, Z, Y, X, others; + % to be CF compliant + sample_data.variables{end+1}.dimensions = 1; + sample_data.variables{end }.name = vars{k}; + sample_data.variables{end }.typeCastFunc = str2func(netcdf3ToMatlabType(imosParameters(sample_data.variables{end}.name, 'type'))); + sample_data.variables{end }.data = sample_data.variables{end}.typeCastFunc(data.(vars{k})); + sample_data.variables{end }.coordinates = coordinates; + sample_data.variables{end }.comment = comment.(vars{k}); +end + +end + +function header = parseProcessedHeader(headerLines, dataHeaderLine) + + header = struct; + + header.nHeaderLines=numel(headerLines)+1; + header.columns = regexp(dataHeaderLine,'\t','split'); + +end + +function [data, comment] = readRCMtxt(dataLines, procHeader) +%readRCMtxt Processes data section from a aanderaa .txt file. +% +% Inputs: +% dataLines - cell array of columns of raw data. +% procHeader - Struct containing processed header. +% +% Outputs: +% data - Struct containing variable data. +% comment - Struct containing variable comment. +% + +narginchk(2,2); + +data = struct; +comment = struct; + +columns = procHeader.columns; +% assume date and time would always be the first and second column +iDate=2; +iDateTimeCol=iDate; +iProcCol=setdiff((1:length(columns)),iDateTimeCol); + +% I don't know how to handle seperate date/time column in loop nicely +% so pull out datetime and set here, and process the other columns in the +% loop. +data.TIME = datenum(dataLines{iDate},'dd.mm.yy HH:MM:SS'); +comment.TIME = 'TIME'; + +for kk = 1:length(iProcCol) + iCol=iProcCol(kk); + + d = dataLines{iCol}; + + [n, d, c] = convertData(genvarname(columns{iCol}), d); + + if isempty(n) || isempty(d), continue; end + + % if the same parameter appears multiple times, + % don't overwrite it in the data struct - append + % a number to the end of the variable name, as + % per the IMOS convention + count = 0; + nn = n; + while isfield(data, nn) + + count = count + 1; + nn = [n '_' num2str(count)]; + end + + data.(nn) = d; + comment.(nn) = c; +end + +end + +function [name, data, comment] = convertData(name, data) +%CONVERTDATA In order to future proof the .txt file, utilize the same ideal +% as for reading SBE37 data. This function is just a big switch statement which takes +% column header as input, and attempts to convert it to IMOS compliant name and +% unit of measurement. Returns empty string/vector if the parameter is not +% supported. + + switch name + + %'Battery Voltage' + case {'BatteryVoltage'}; + name = 'VOLT'; + comment = 'Battery Voltage'; + + %'Absolute Current Speed' + case {'AbsSpeed'}; + name = 'CSPD'; + data = data / 100; % current in cm/s + comment = ''; + + %'Direction' + case {'Direction'}; + name = 'CDIR_MAG'; + comment = ''; + + %'North' + case {'North'}; + name = 'VCUR_MAG'; + data = data / 100; % current in cm/s + comment = ''; + + %'East' + case {'East'}; + name = 'UCUR_MAG'; + data = data / 100; % current in cm/s + comment = ''; + + %'Heading' + case {'Heading'}; + name = 'HEADING_MAG'; + comment = ''; + + %'Tilt X' + case {'TiltX'}; + name = 'ROLL'; + comment = ''; + + %'Tilt Y' + case {'TiltY'}; + name = 'PITCH'; + comment = ''; + + %'Single-Ping Standard deviation' + case {'SPStd'}; + name = 'CSPD_STD'; + data = data / 100; % current in cm/s + comment = ''; + + %'Signal Strength' + case {'Strength'}; + name = 'ABSI'; + comment = ''; + + otherwise + name = ''; + data = []; + comment = ''; + end +end + diff --git a/Parser/readVemcoCsv.m b/Parser/readVemcoCsv.m index 8ea421882..b728702f4 100644 --- a/Parser/readVemcoCsv.m +++ b/Parser/readVemcoCsv.m @@ -55,7 +55,7 @@ iDate=1; iTime=2; iDateTimeCol=[iDate iTime]; -iProcCol=setdiff([1:length(columns)],iDateTimeCol); +iProcCol=setdiff((1:length(columns)),iDateTimeCol); % I don't know how to handle seperate date/time column in loop nicely % so pull out datetime and set here, and process the other columns in the @@ -68,7 +68,7 @@ d = dataLines{iCol}; - [n, d, c] = convertData(genvarname(columns{iCol}), d, procHeader); + [n, d, c] = convertData(genvarname(columns{iCol}), d); if isempty(n) || isempty(d), continue; end @@ -90,7 +90,7 @@ end -function [name, data, comment] = convertData(name, data, procHeader) +function [name, data, comment] = convertData(name, data) %CONVERTDATA In order to future proof the .csv file, utilize the same ideal % as for reading SBE37 data. This function is just a big switch statement which takes % column header as input, and attempts to convert it to IMOS compliant name and diff --git a/Util/getpos.m b/Util/getpos.m index 19f6256ab..3302e4250 100644 --- a/Util/getpos.m +++ b/Util/getpos.m @@ -46,7 +46,9 @@ % % Check the number of input arguments -error(nargchk(1,4, nargin)); + +narginchk(1,4); + % Check if H is a graphics object handle if ~ishandle(h) diff --git a/Util/legendflex.m b/Util/legendflex.m index 3eed973a7..351a6aee4 100644 --- a/Util/legendflex.m +++ b/Util/legendflex.m @@ -166,7 +166,22 @@ % 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] +% (or squishing, via use of negative values) things via this +% parameter. [2 1 1] +% +% nolisten: logical scalar. If true, don't add the event listeners. +% The event listeners update the legend objects when you +% change a property of the labeled objects (such as line +% style, color, etc.). However, the updating requires the +% legend to be redrawn, which can really slow things down, +% especially if you're labelling lots of objects that get +% changed together (if you change the line width of 100 +% labeled lines, the legend gets redrawn 100 times). In more +% recent releases, this also occurs when printing to file, so +% I recommend setting this to true if you plan to print a +% legend with a large number of labeled objects. The legend +% will still be redrawn on figure resize regardless of the +% value of this parameter. [false] % % In addition to these legendflex-specific parameters, this function will % accept any parameter accepted by the original legend function (e.g. @@ -215,6 +230,8 @@ % Detemine whether HG2 is in use hg2flag = ~verLessThan('matlab', '8.4.0'); +r2013bflag = ~verLessThan('matlab', '8.2.0'); +r2016aflag = ~verLessThan('matlab', '9.0.0'); %------------------- % Parse input @@ -262,16 +279,23 @@ 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]})); +if r2013bflag + addParamMethodName = 'addParameter'; +else + addParamMethodName = 'addParamValue'; +end +p.(addParamMethodName)('xscale', 1, @(x) validateattributes(x, {'numeric'}, {'nonnegative','scalar'})); +p.(addParamMethodName)('ncol', 0, @(x) validateattributes(x, {'numeric'}, {'scalar', 'integer'})); +p.(addParamMethodName)('nrow', 0, @(x) validateattributes(x, {'numeric'}, {'scalar', 'integer'})); +p.(addParamMethodName)('ref', defref, @(x) validateattributes(x, {'numeric','handle'}, {'scalar'})); +p.(addParamMethodName)('anchor', [3 3], @(x) validateattributes(x, {'numeric','cell'}, {'size', [1 2]})); +p.(addParamMethodName)('buffer', [-10 -10], @(x) validateattributes(x, {'numeric'}, {'size', [1 2]})); +p.(addParamMethodName)('bufferunit', 'pixels', @(x) validateattributes(x, {'char'}, {})); +p.(addParamMethodName)('box', 'on', @(x) validateattributes(x, {'char'}, {})); +p.(addParamMethodName)('title', '', @(x) validateattributes(x, {'char','cell'}, {})); +p.(addParamMethodName)('padding', [2 1 1], @(x) validateattributes(x, {'numeric'}, {'size', [1 3]})); % 'nonnegative' +p.(addParamMethodName)('nolisten', false, @(x) validateattributes(x, {'logical'}, {'scalar'})); +p.(addParamMethodName)('parent', gcf); p.KeepUnmatched = true; @@ -304,8 +328,30 @@ % 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); +if r2016aflag + % The new legend objects are pretty opaque... even diving into the + % undocumented properties, I haven't been able to find the handles of + % the legend sub-components (lines, text, etc). So I need to stick to + % the legacy version, which creates an axis object rather than legend + % object. Legacy version has bug in text properties parsing, though, so + % need to work around that too: use the new-style legend object to get + % proper text properties, then use those to alter the buggy old-style + % legend. + tmp = legend(legin{:}, extra{:}, 'location', 'northeast'); + textProps = {'FontAngle','FontName','FontSize','FontUnits','FontWeight','Interpreter'}; + tprop = get(tmp, textProps); + delete(tmp); + wtmp = warning('off', 'MATLAB:handle_graphics:exceptions:SceneNode'); % silence Latex interpreter thing + [h.leg, h.obj, h.labeledobj, h.textstr] = legend(legin{:}, extra{:}, 'location', 'northeast'); + warning(wtmp); + nobj = length(h.labeledobj); + for it = 1:length(textProps) + set(h.obj(1:nobj), textProps{it}, tprop{it}); + end +else + [h.leg, h.obj, h.labeledobj, h.textstr] = legend(legin{:}, extra{:}, 'location', 'northeast'); + nobj = length(h.labeledobj); +end warning(S); if nobj == 0 @@ -318,10 +364,11 @@ % 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. +% necessary. Issue appears to be fixed in 2015b. iscont = strcmp(get(h.labeledobj, 'type'), 'contour'); -cbugflag = ~verLessThan('matlab', '8.4.0') && any(iscont); +cbugflag = ~verLessThan('matlab', '8.4.0') && verLessThan('matlab', '8.6.0') && any(iscont); + if cbugflag if length(legin) == 1 @@ -508,7 +555,7 @@ 'xtick', [], ... 'ytick', [], ... 'box', 'on', ... - 'parent', figh); + 'parent', Opt.parent); % Copy the text strings to the new legend @@ -601,6 +648,33 @@ set(figh, 'currentaxes', currax); drawnow; % Not sure why this is necessary for the currentaxes to take effect, but it is +% Fix for vertical-alignment issue: This solution still isn't perfect, but +% it seems to help for most Interpreter-none and Interpreter-latex cases. +% The TeX interpreter still places sub- and superscripts too high/low... no +% robust fix found for that yet. +% +% TODO: need to add proper calcs for when title included +% +% Thanks to S�ren Enemark for this suggestion. + +if ~addtitle + try % TODO: Crashing on some edge cases + textobj = hnew.obj(1:nobj); + yheight = get(hnew.leg, 'ylim'); + yheight = yheight(2); + + ylo = get(textobj(Opt.nrow), 'extent'); + ylo = ylo(2); + yhi = get(textobj(1), 'extent'); + yhi = sum(yhi([2 4])); + dy = yheight/2 - 0.5*(ylo + yhi); + for ii = 1:length(textobj) + pos = get(textobj(ii), 'position'); + set(textobj(ii), 'position', pos + [0 dy 0]); + end + end +end + %------------------- % Callbacks and % listeners @@ -623,8 +697,9 @@ % Resize listeners addlistener(hnew.leg, 'Position', 'PostSet', @(src,evt) updatelegappdata(src,evt,hnew.leg)); -if hg2flag && strcmp(Lf.ref.Type, 'figure') +if hg2flag && (strcmp(Lf.ref.Type, 'figure') || (strcmp(Lf.ref.Type, 'uipanel'))) addlistener(Lf.ref, 'SizeChanged', @(src,evt) updatelegpos(src,evt,hnew.leg)); + addlistener(Lf.ref, 'LocationChanged', @(src,evt) updatelegpos(src,evt,hnew.leg)); else addlistener(Lf.ref, 'Position', 'PostSet', @(src,evt) updatelegpos(src,evt,hnew.leg)); end @@ -642,19 +717,23 @@ end end -% Run the resync function if anything changes with the labeled objects +if ~Opt.nolisten + + % Run the resync function if anything changes with the labeled objects -objwatch = findall(h.labeledobj, 'type', 'line', '-or', 'type', 'patch'); + 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)); + 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 + end diff --git a/Util/setpos.m b/Util/setpos.m index c474036f3..3bf5539a3 100644 --- a/Util/setpos.m +++ b/Util/setpos.m @@ -48,7 +48,9 @@ function setpos(h,fmt,href) % % Check the number of input arguments -error(nargchk(2,3, nargin)); + +narginchk(2,3); + % Check if H is a graphics object handle if ~ishandle(h) diff --git a/imosToolbox.m b/imosToolbox.m index fd0ea0ddb..40f4241f7 100644 --- a/imosToolbox.m +++ b/imosToolbox.m @@ -73,7 +73,7 @@ function imosToolbox(auto, varargin) end % Set current toolbox version -toolboxVersion = ['2.5.21 - ' computer]; +toolboxVersion = ['2.5.22 - ' computer]; switch auto case 'auto', autoIMOSToolbox(toolboxVersion, varargin{:}); diff --git a/imosToolbox_Linux64.bin b/imosToolbox_Linux64.bin index bc072efd5..f22a14964 100755 Binary files a/imosToolbox_Linux64.bin and b/imosToolbox_Linux64.bin differ diff --git a/imosToolbox_Win32.exe b/imosToolbox_Win32.exe index ef3a7e2b4..61ad63024 100644 Binary files a/imosToolbox_Win32.exe and b/imosToolbox_Win32.exe differ diff --git a/imosToolbox_Win64.exe b/imosToolbox_Win64.exe index f10b1f355..4448d938b 100644 Binary files a/imosToolbox_Win64.exe and b/imosToolbox_Win64.exe differ