function hL = dsplot(x, y, numPoints) %DSPLOT Create down sampled plot. % This function creates a down sampled plot to improve the speed of % exploration (zoom, pan). % % DSPLOT(X, Y) plots Y versus X by downsampling if there are large number % of elements. X and Y needs to obey the following: % 1. X must be a monotonically increasing vector. % 2. If Y is a vector, it must be the same size as X. % 3. If Y is a matrix, one of the dimensions must line up with X. % % DSPLOT(Y) plots the columns of Y versus their index. % % hLine = DSPLOT(X, Y) returns the handles of the line. Note that the % lines may be downsampled, so they may not represent the full data set. % % DSPLOT(X, Y, NUMPOINTS) or DSPLOT(Y, [], NUMPOINTS) specifies the % number of points (roughly) to display on the screen. The default is % 50000 points (~390 kB doubles). NUMPOINTS can be a number greater than % 500. % % It is very likely that more points will be displayed than specified by % NUMPOINTS, because it will try to plot any outlier points in the range. % If the signal is stochastic or has a lot of sharp changes, there will % be more points on plotted on the screen. % % The figure title (name) will indicate whether the plot shown is % downsampled or is the true representation. % % The figure can be saved as a .fig file, which will include the actual % data. The figure can be reloaded and the actual data can be exported to % the base workspace via a menu. % % Run the following examples and zoom/pan to see the performance. % % Example 1: (with small details) % x = linspace(0, 2*pi, 1000000); % y1 = sin(x)+.02*cos(200*x)+0.001*sin(2000*x)+0.0001*cos(20000*x); % dsplot(x,y1);title('Down Sampled'); % % compare with % figure;plot(x,y1);title('Normal Plot'); % % Example 2: (with outlier points) % x = linspace(0, 2*pi, 1000000); % y1 = sin(x) + .01*cos(200*x) + 0.001*sin(2000*x); % y2 = sin(x) + 0.3*cos(3*x) + 0.001*randn(size(x)); % y1([300000, 700000, 700001, 900000]) = [0, 1, -2, 0.5]; % y2(300000:500000) = y2(300000:500000) + 1; % y2(500001:600000) = y2(500001:600000) - 1; % y2(800000) = 0; % dsplot(x, [y1;y2]);title('Down Sampled'); % % compare with % figure;plot(x, [y1;y2]);title('Normal Plot'); % % See also PLOT. % Version: % v1.0 - first version (Aug 1, 2007) % v1.1 - added CreateFcn for the figure so that when the figure is saved % and re-loaded, the zooming and panning works. Also added a menu % item for saving out the original data back to the base % workspace. (Aug 10, 2007) % % Jiro Doke % August 1, 2007 debugMode = false; %-------------------------------------------------------------------------- % Error checking error(nargchk(1, 3, nargin, 'struct')); if nargin < 3 % Number of points to show on the screen. It's quite possible that more % points will be displayed if there are outlier points numPoints = 50000; % ~390 kB for doubles end if nargin == 1 || isempty(y) noXVar = true; y = x; x = []; else noXVar = false; end myErrorCheck; %-------------------------------------------------------------------------- if size(x, 2) > 1 % it's a row vector -> transpose x = x'; y = y'; varTranspose = true; else varTranspose = false; end % Number of lines numSignals = size(y, 2); % If the number of lines is greater than the number of data points per % line, it's possible that the user may have mistaken the matrix % orientation. if numSignals > size(y, 1) s = input(sprintf('Are you sure you want to plot %d lines? (y/n) ', ... numSignals), 's'); if ~strcmpi(s, 'y') disp('Canceled. You may want to transpose the matrix.'); if nargout == 1 hL = []; end return; end end % Attempt to find outliers. Use a running average technique filterWidth = ceil(min([50, length(x)/10])); % max window size of 50 a = y - filter(ones(filterWidth,1)/filterWidth, 1, y); [iOutliers, jOutliers] = find(abs(a - repmat(mean(a), size(a, 1), 1)) > ... repmat(4 * std(a), size(a, 1), 1)); clear a; % Always create new figure because it messes around with zoom, pan, % datacursors. hFig = figure; figName = ''; % Create template plot using NaNs hLine = plot(NaN(2, numSignals), NaN(2, numSignals)); set(hLine, 'tag', 'dsplot_lines'); % Define CreateFcn for the figure set(hFig, 'CreateFcn', @mycreatefcn); mycreatefcn(); % Create menu for exporting data hMenu = uimenu(hFig, 'Label', 'Data'); uimenu(hMenu, ... 'Label' , 'Export data to workspace.', ... 'Callback', @myExportFcn); % Update lines updateLines([min(x), max(x)]); % Deal with output argument if nargout == 1 hL = hLine; end %-------------------------------------------------------------------------- function myExportFcn(varargin) % This callback allows for extracting the actual data from the figure. % This means that if you save this figure and load it back later, you % can get back the data. % Determine the variable name allVarNames = evalin('base', 'who'); newVarName = genvarname('dsplotData', allVarNames); % X if ~noXVar if varTranspose dat.x = x'; else dat.x = x; end end % Y if varTranspose dat.y = y'; else dat.y = y; end assignin('base', newVarName, dat); msgbox(sprintf('Data saved to the base workspace as ''%s''.', ... newVarName), 'Saved', 'modal'); end %-------------------------------------------------------------------------- function mycreatefcn(varargin) % This callback defines the custom zoom/pan functions. It is defined as % the CreateFcn of the figure, so it allows for saving and reloading of % the figure. if nargin > 0 hFig = varargin{1}; end hLine = findobj(hFig, 'type', 'axes'); hLine(strmatch('legend', get(hLine, 'tag'))) = []; hLine = get(hLine, 'Children'); % Create Zoom, Pan, Datacursor objects hZoom = zoom(hFig); hPan = pan(hFig); hDc = datacursormode(hFig); set(hZoom, 'ActionPostCallback', @mypostcallback); set(hPan , 'ActionPostCallback', @mypostcallback); set(hDc , 'UpdateFcn' , @myDCupdatefcn); end %-------------------------------------------------------------------------- function mypostcallback(obj, evd) %#ok % This callback that gets called when the mouse is released after % zooming or panning. % single or double-click switch get(hFig, 'SelectionType') case {'normal', 'alt'} updateLines(xlim(evd.Axes)); case 'open' updateLines([min(x), max(x)]); end end %-------------------------------------------------------------------------- function updateLines(rng) % This helper function is for determining the points to plot on the % screen based on which portion is visible in the current limits. % find indeces inside the range id = find(x >= rng(1) & x <= rng(2)); % if there are more points than we want if length(id) > numPoints / numSignals % see how many outlier points are in this range blah = iOutliers > id(1) & iOutliers < id(end); % determine indeces of points to plot. idid = round(linspace(id(1), id(end), round(numPoints/numSignals)))'; x2 = cell(numSignals, 1); y2 = x2; for iSignals = 1:numSignals % add outlier points ididid = unique([idid; iOutliers(blah & jOutliers == iSignals)]); x2{iSignals} = x(ididid); y2{iSignals} = y(ididid, iSignals); end if debugMode figName = ['downsampled - ', sprintf('%d, ', cellfun('length', y2))]; else figName = 'downsampled'; end else % no need to down sample figName = 'true'; x2 = repmat({x(id)}, numSignals, 1); y2 = mat2cell(y(id, :), length(id), ones(1, numSignals))'; end % Update plot set(hLine, {'xdata', 'ydata'} , [x2, y2]); set(hFig, 'Name', figName); end %-------------------------------------------------------------------------- function txt = myDCupdatefcn(empt, event_obj) %#ok % This function displays appropriate data cursor message based on the % display type pos = get(event_obj,'Position'); switch figName case 'true' txt = {['X: ',num2str(pos(1))],... ['Y: ',num2str(pos(2))]}; otherwise txt = {['X: ',num2str(pos(1))],... ['Y: ',num2str(pos(2))], ... 'Warning: Downsampled', ... 'May not be accurate'}; end end %-------------------------------------------------------------------------- function myErrorCheck % Do some error checking on the input arguments. if ~isa(numPoints, 'double') || numel(numPoints) > 1 || numPoints < 500 error('Third argument must be a scalar greater than 500'); end if ~isnumeric(x) || ~isnumeric(y) error('Arguments must be numeric'); end if length(size(x)) > 2 || length(size(y)) > 2 error('Only 2-D data accepted'); end % If only one input, create index vector X if isempty(x) if ismember(1, size(y)) x = reshape(1:numel(y), size(y)); else x = (1:size(y, 1))'; end end if ~ismember(1, size(x)) error('First argument has to be a vector'); end if ~isequal(size(x, 1), size(y, 1)) && ~isequal(size(x, 2), size(y, 2)) error('One of the dimensions of the two arguments must match'); end if any(diff(x) <= 0) error('The first argument has to be a monotonically increasing vector'); end end end