function varargout = simplot_mod(time, data, ports, diffStr) %SIMPLOT Plot data structures output from Simulink. % SIMPLOT(DATA) creates Scope-like graphics in a Handle Graphics window. % DATA is either a data structure or matrix of the form produced by Simulink % output blocks. Colors are set to match those used in the Simulink Scope. % % If DATA is a "Matrix" or "Structure without time", a separate time vector % can be specified. TIME is a vector with the same length as the data % signals in DATA and is entered as the first input argument. TIME has no % effect if DATA is a "Structure with time". For example: % % SIMPLOT(TIME, DATA) % % plots DATA against the TIME. % % If DATA is a structure produced by a multi-port Scope block, the data from % each port are displayed in separate subplots. A vector of port indices can % be used to select specific ports. For example: % % PORTS = [1,3]; % SIMPLOT(DATA, PORTS) % % plots the data from the 1st and 3rd ports. % % If DATA is a cell array of structures or matrices, the plots from each % element are overlayed so that comparisons can be made between multiple runs. % Each run is assumed to have identical structure. Line styles are used to % differentiate between runs. For example: % % DATA = {RUN1, RUN2}; % NOTE: Cell array - {} % SIMPLOT(DATA); % % overlays the data from RUN1 and RUN2. % % If DATA contains "Matrices" or "Structures without time", the data sets for % all runs must be the same size. % % The 'diff' flag is used to display the difference between multiple runs. The % 1st run is subtracted from subsequent runs and the results are plotted with % the line style of the run being compared. Linear interpolation is used if % time vectors are not identical. If start and stop times differ between runs, % the difference is only plotted for the region of overlap. For example: % % DATA = {RUN1 RUN2}; % SIMPLOT(DATA, 'diff') % % plots RUN2-RUN1 using the line style for RUN2. % % COMBINING INPUT ARGUMENT OPTIONS: % --------------------------------- % The options described above can be used in various combinations. % All input arguments, apart from DATA, are optional but when included must be % entered in the following order: % % SIMPLOT(TIME, DATA, PORTS, 'diff') % % OBTAINING OBJECT HANDLES: % ------------------------- % HFIG = SIMPLOT(DATA) returns the handle to the figure. % % [HFIG, HAXES, HLINES] = SIMPLOT(DATA) returns the handles to the figure, % axes and lines plotted respectively. % % Some common uses for these handles include: % WHITEBG(HFIG) to use standard MATLAB figure colors. % SET(HAXES, 'NextPlot', 'add') to turn HOLD ON for all axes. % SET(HLINES, 'LineStyle', '-') to set all line styles to solid lines. % % See also PLOT. % Copyright 1990-2002 The MathWorks, Inc. % $Revision: 1.11 $ % =========================================================================== % ERROR CHECKING % =========================================================================== % Check validity of 1st input argument: switch nargin case 1 if isempty(time) error('Empty input argument.') elseif (iscell(time) | isstruct(time) | isnumeric(time)) % ({Struct}) | ({MATRIX}) | (Struct) | (MATRIX) data = time; clear time; else error('Invalid input argument for use with SIMPLOT.') end case 2 if isempty(time) | isempty(data) error('Empty input argument(s).') elseif (iscell(time) & strcmpi(data, 'diff')) % ({Struct}, 'diff') | ({MATRIX}, 'diff') diffStr = 'diff'; data = time; clear time; elseif iscell(time) & isnumeric(data) % ({Struct}, [PORTS]) if ~all(cellfun('isclass', time, 'struct')) error('PORTS only valid for data in structure format.') end ports = data; data = time; clear time; elseif isstruct(time) & isnumeric(data) % (Struct, [PORTS]) ports = data; data = time; clear time; elseif isnumeric(time) & isnumeric(data) % (time, MATRIX) % *** CHECK AGAINST (MATRIX, [PORTS]) *** if all(size(time)~=1) % time must be a vector error('PORTS only valid for data in structure format.') elseif size(data, 1)==1 % expect column vector for data error('PORTS only valid for data in structure format.') end elseif ~(isnumeric(time) & (iscell(data) | isstruct(data))) % (time, {Struct}) | (time, {MATRIX}) | (time, Struct) error('Invalid input argument(s) for use with SIMPLOT.') end case 3 if isempty(time) | isempty(data) | isempty(ports) error('Empty input argument(s).') elseif strcmpi(ports, 'diff') diffStr = 'diff'; if iscell(time) % ({Struct}, [PORTS], 'diff') if ~all(cellfun('isclass', time, 'struct')) error('PORTS only valid for data in structure format.') end ports = data; data = time; clear time; elseif isnumeric(time) & iscell(data) diffStr = 'diff'; clear ports else % (time, {Struct}, 'diff') | (time, {MATRIX}, 'diff') error('Invalid input argument(s) for use with SIMPLOT.') end elseif isnumeric(time) & iscell(data) & isnumeric(ports) % (time, {Struct}, [PORTS]) if ~all(cellfun('isclass', data, 'struct')) error('PORTS only valid for data in structure format.') end elseif ~(isnumeric(time) & isstruct(data) & isnumeric(ports)) % (time, Struct, [PORTS]) error('Invalid input argument(s) for use with SIMPLOT.') end case 4 if isempty(time) | isempty(data) | isempty(ports) | isempty(diffStr) error('Empty input argument(s).') elseif isnumeric(time) & iscell(data) & isnumeric(ports) & strcmpi(diffStr, 'diff') % (time, {Struct}, [PORTS], 'diff') if ~all(cellfun('isclass', data, 'struct')) error('PORTS only valid for data in structure format.') end else error('Invalid input argument(s) for use with SIMPLOT.') end otherwise error('SIMPLOT must have between 1 and 4 input arguments.') end % Convert all data to cell array of structures. if iscell(data) if ~all(cellfun('isclass', data, 'struct')) % {Struct} for i = 1:length(data) if isnumeric(data{i}) % {MATRIX} % Assume that no time vector is provided. %if all(size(data{i})~=1) & all(diff(data{i}(:,1)>0)) %Check for time vector in 1st column % warning(['Using first column of data as time vector for run #', num2str(i), '.']) % sigStruct = struct('values', data{i}(:,2:end), 'label', ''); % data{i} = struct('time', data{i}(:,1), 'signals', sigStruct); %else % No Time Vector sigStruct = struct('values', data{i}, 'label', ''); data{i} = struct('time', [], 'signals', sigStruct); %end else error(sprintf(['Invalid input argument(s) for use with SIMPLOT.\n', ... 'DATA must contain either all structures or all matrices.'])) end end end elseif isstruct(data) & all(size(data)==1) % Struct data = {data}; elseif isnumeric(data) % MATRIX % Assume that no time vector is provided. %if size(data)~=1 & all(diff(data(:,1)>0)) %Check for time vector in 1st column % warning(['Using first column of data as time vector for run #', num2str(i), '.']) % sigStruct = struct('values', data(:,2:end), 'label', ''); % data = {struct('time', data(:,1), 'signals', sigStruct)}; %else % No Time Vector sigStruct = struct('values', data, 'label', ''); data = {struct('time', [], 'signals', sigStruct)}; %end else error(sprintf(['Invalid input argument(s) for use with SIMPLOT.\n', ... 'DATA must be either a structure or a matrix.'])) end % Check size of "time" if exist('time') == 1 if any(size(time)==1) time = time(:); else error('TIME must be a vector.') end end % Check contents of "data" nStructs = length(data); for i=1:nStructs % Check for essential fields within data structure. if ~(isfield(data{i},'time') & isfield(data{i},'signals') & ... isfield(data{i}.signals(1),'values') & isfield(data{i}.signals(1),'label')) error(['Essential fields missing from data structure for run #', num2str(i), '.', ... sprintf('\n'), 'Data structure format must match those output from Simulink.']) end % Handle matrix signals ("values" field contains 3-D matrix) % - "dimensions" contains [nRowsOfMatrixSignal nColsOfMatrixSignal] % - "values" field contains 3-D matrix (1 page per time step). % NOTE: If there is only 1 time step, "values" contains a 2-D matrix % - Convert to 2-D matrix (1 row per time step). for j = 1:length(data{i}.signals) if isfield(data{i}.signals(j), 'dimensions') dimsVal = data{i}.signals(j).dimensions; if size(dimsVal, 2) == 2 % Check that "values" contains 3-D matrix if ndims(data{i}.signals(j).values) == 3 sizeVal = size(data{i}.signals(j).values); nCols = sizeVal(1)*sizeVal(2); nRows = sizeVal(3); data{i}.signals(j).values = ... reshape(data{i}.signals(j).values, [nCols nRows])'; else % (Only one time step - "values" contains 2-D matrix) data{i}.signals(j).values = (data{i}.signals(j).values(:))'; end end end end if i == 1 % Check validity of sizes & dimensions of structure for 1st run sizeSigs = size(data{1}.signals); if sizeSigs(1) ~= 1 error(['Expected "signals" field to contain 1xN data structure for run #', num2str(i), '.', ... sprintf('\n'), 'Data structure format must match those output from Simulink.']) end nDimsVal = cellfun('ndims', {data{1}.signals.values}); if any(nDimsVal ~= 2) error(['Expected "values" field to contain 2-d matrix for run #', num2str(i), '.', ... sprintf('\n'), 'Data structure format must match those output from Simulink.']) end nCols = cellfun('size', {data{1}.signals.values}, 2); else % Check that all other runs match 1st run (i~=1) % Check number of signals in each run (must match 1st run) if ~isequal(size(data{i}.signals), sizeSigs) error(['Size of data structures differs between run #1 and run #', num2str(i), '.']) end % Check signal widths for each run (must equal 1st run) if (~isequal(cellfun('ndims',{data{i}.signals.values}), nDimsVal) | ... ~isequal(cellfun('size', {data{i}.signals.values},2),nCols)) error(['Signal widths differ between run #1 and run #', num2str(i), '.']); end end % Check time vector for each run. if isempty(data{i}.time) & exist('time')==1 data{i}.time = time; end if isempty(data{i}.time) if ~(exist('noTimeVector')==1) if i > 1 error(['Time vector is defined for previous runs but not for run #', num2str(i), '.']) else noTimeVector = 1; end elseif size(data{i}.signals(1).values,1) ~= size(data{noTimeVector}.signals(1).values) error(['No time vectors defined.', sprintf('\n'), ... 'Size mismatch between data for run #1 and run #', num2str(i), '.']); end elseif exist('noTimeVector')==1 error(['Time vector is defined for run #', num2str(i), ' but not for previous runs.']) elseif ndims(data{i}.time) ~= 2 | size(data{i}.time, 2) ~= 1 | any(diff(data{i}.time) <= 0) | ... ~all(size(data{i}.time, 1) == cellfun('size', {data{i}.signals.values}, 1)) error(['Invalid time vector for run #', num2str(i), '.']); end end % Set "ports" if not already set. if ~(exist('ports')==1) ports = 1:length(data{1}.signals); elseif ~isnumeric(ports) | any(rem(ports,1)) | any(ports<1) | any(ports>sizeSigs(2)) error('Port indices exceed number of signals.'); end % =========================================================================== % DIFFERENCE CALCULATIONS - INTERPOLATING IF NECESSARY % =========================================================================== if exist('diffStr') == 1 if nStructs < 2, error('Must specify more than one run to plot differences.'); end % Check whether time vectors are all identical doInterp = zeros(nStructs,1); if ~isempty(data{1}.time) for i=2:nStructs, if length(data{i}.time) ~= length(data{1}.time) doInterp(i) = 1; elseif data{i}.time ~= data{1}.time doInterp(i) = 1; end % Produce warning if start and stop times are different. if ((data{i}.time(1) == 0) | (data{1}.time(1) == 0)), if (data{i}.time(1) - data{1}.time(1))^2 > eps, warning(['Start time differs between run #1 and run #', num2str(i), '.', sprintf('\n'), ... 'Differences only plotted for overlapping region.', sprintf('\n')]); end elseif ((data{i}.time(1) - data{1}.time(1))/data{1}.time(1))^2 > eps, warning(['Start time differs between run #1 and run #', num2str(i), '.', sprintf('\n'), ... 'Differences only plotted for overlapping region.', sprintf('\n')]); end if ((data{i}.time(end) == 0) | (data{1}.time(end) == 0)), if (data{i}.time(end) - data{1}.time(end))^2 > eps, warning(['Stop time differs between run #1 and run #', num2str(i), '.', sprintf('\n'), ... 'Differences only plotted for overlapping region.', sprintf('\n')]); end elseif ((data{i}.time(end) - data{1}.time(end))/data{1}.time(end))^2 > eps, warning(['Stop time differs between run #1 and run #', num2str(i), '.', sprintf('\n'), ... 'Differences only plotted for overlapping region.', sprintf('\n')]); end end end for i=2:nStructs, if doInterp(i) == 1; % Do differences with interpolation warning(['Time vectors differ between run #1 and run #', num2str(i), '.', sprintf('\n'), ... 'Performing linear interpolation.', sprintf('\n')]) tMin = max(data{i}.time(1), data{1}.time(1)); tMax = min(data{i}.time(end), data{1}.time(end)); tStep = (tMax-tMin)/(max(length(data{i}.time), length(data{1}.time))-1); for j=ports, tValid = (tMin:tStep:tMax)'; dataValid = interp1(data{i}.time, data{i}.signals(j).values, tValid, 'linear'); data1Valid = interp1(data{1}.time, data{1}.signals(j).values, tValid, 'linear'); data{i}.signals(j).values = dataValid - data1Valid; end data{i}.time = tValid; else % Do differences without interpolation for j=ports, data{i}.signals(j).values = data{i}.signals(j).values - data{1}.signals(j).values; end end end end % =========================================================================== % CREATE GRAPHICS % =========================================================================== % Set necessary properties to match Simulink Scope. figColor = [0.5 0.5 0.5]; axesColor = 'k'; ticColor = 'w'; axesColorOrder = [1 1 0 1 0 1 0 1 1 1 0 0 0 1 0 0 0 1]; % Set figure properties hFig = gcf; % set(hFig, 'Color', figColor, ... % 'DefaultAxesColor', axesColor,... % 'DefaultAxesXColor', ticColor, ... % 'DefaultAxesYColor', ticColor, ... % 'DefaultAxesZColor', ticColor, ... % 'DefaultTextColor', ticColor); nAxes = length(ports); % Determine which axes need to be reset to "replace" once finished for i=1:nAxes, hAxes(i,1) = subplot(nAxes, 1, i); if strcmpi(get(hAxes(i), 'NextPlot'), 'replace') cla; end end axesToClear = findobj(hFig, 'Type', 'Axes', 'NextPlot', 'replace'); % Set axis properties % set(hAxes, 'NextPlot', 'add', ... % 'XGrid', 'on', ... % 'YGrid', 'on', ... % 'Color', axesColor, ... % 'ColorOrder', axesColorOrder, ... % 'XColor', ticColor, ... % 'YColor', ticColor, ... % 'Box', 'on', ... % 'View', [0 90]); set(hAxes, 'NextPlot', 'add', ... 'XGrid', 'on', ... 'YGrid', 'on', ... 'Box', 'on', ... 'View', [0 90]); % Create the plots. if ~(exist('diffStr')==1), for i=1:nStructs, i_PlotScopeStruct(data{i}, ports, i); end else, for i=2:nStructs, i_PlotScopeStruct(data{i}, ports, i); end end % Reset axes to initial conditions set(axesToClear, 'NextPlot', 'replace'); % =========================================================================== % CREATE RETURN ARGUMENTS. % =========================================================================== switch nargout, case 1, varargout = {hFig}; case 2, varargout = {hFig, hAxes}; case 3, hLines = findobj(gcf, 'Type', 'line'); varargout = {hFig, hAxes, hLines}; end % =========================================================================== % SUB-FUNCTIONS % =========================================================================== % --------------------------------------------------------------------------- % I_PLOTSCOPESTRUCT Overlay the plots from the specified scope logVar. % --------------------------------------------------------------------------- function i_PlotScopeStruct(data, ports, runNo) nAxes = length(ports); lineStyles = {'-' '-.' '--' ':' '.'}; if isempty(data.time) xString = 'Indices'; data.time = (1:size(data.signals(1).values, 1))'; else xString = 'Time'; end for i=1:nAxes, hAx = subplot(nAxes, 1, i); port = ports(i); nLines = size(data.signals(port).values, 2); hLines = plot(ones(2,nLines)*NaN, 'LineStyle', lineStyles{rem(runNo-1,length(lineStyles))+1}); if isfield(data.signals(port), 'plotStyle') plotStyle = reshape(data.signals(port).plotStyle,1,nLines); else plotStyle = zeros(1,nLines); end for j=1:nLines, if ~plotStyle(j), set(hLines(j), 'Xdata', data.time, 'Ydata', data.signals(port).values(:,j), ... 'Tag', ['Run #', num2str(runNo), ' - ', 'data', num2str(j)]); else, [Xdata, Ydata] = stairs(data.time, data.signals(port).values(:,j)); set(hLines(j), 'Xdata', Xdata, 'Ydata', Ydata, ... 'Tag', ['Run #', num2str(runNo), ' - ', 'data', num2str(j)]); end end hTitle = get(hAx, 'Title'); hYLabel = get(hAx, 'YLabel'); hXLabel = get(hAx, 'XLabel'); if isfield(data.signals(port), 'title') & ~isempty(data.signals(port).title) set(hTitle, 'String', data.signals(port).title, ... 'Color', get(hAx, 'XColor'), ... 'Interpreter', 'none'); set(hYLabel, 'String', ''); set(hXLabel, 'String', ''); else set(hTitle, 'String', ''); set(hYLabel, 'String', data.signals(port).label, ... 'Interpreter', 'none'); set(hXLabel, 'String', ''); end end % Set XLabel for last subplot set(hXLabel, 'String', xString); % Bring Figure to front shg; % END OF SUBFUNCTION