function varargout = boundedline(varargin) %BOUNDEDLINE Plot a line with shaded error/confidence bounds % % [hl, hp] = boundedline(x, y, b) % [hl, hp] = boundedline(x, y, b, linespec) % [hl, hp] = boundedline(x1, y1, b1, linespec1, x2, y2, b2, linespec2) % [hl, hp] = boundedline(..., 'alpha') % [hl, hp] = boundedline(..., ax) % [hl, hp] = boundedline(..., 'transparency', trans) % [hl, hp] = boundedline(..., 'orientation', orient) % [hl, hp] = boundedline(..., 'cmap', cmap) % % Input variables: % % x, y: x and y values, either vectors of the same length, matrices % of the same size, or vector/matrix pair where the row or % column size of the array matches the length of the vector % (same requirements as for plot function). % % b: npoint x nside x nline array. Distance from line to % boundary, for each point along the line (dimension 1), for % each side of the line (lower/upper or left/right, depending % on orientation) (dimension 2), and for each plotted line % described by the preceding x-y values (dimension 3). If % size(b,1) == 1, the bounds will be the same for all points % along the line. If size(b,2) == 1, the bounds will be % symmetrical on both sides of the lines. If size(b,3) == 1, % the same bounds will be applied to all lines described by % the preceding x-y arrays (only applicable when either x or % y is an array). Bounds cannot include Inf, -Inf, or NaN, % % linespec: line specification that determines line type, marker % symbol, and color of the plotted lines for the preceding % x-y values. % % 'alpha': if included, the bounded area will be rendered with a % partially-transparent patch the same color as the % corresponding line(s). If not included, the bounded area % will be an opaque patch with a lighter shade of the % corresponding line color. % % ax: handle of axis where lines will be plotted. If not % included, the current axis will be used. % % transp: Scalar between 0 and 1 indicating with the transparency or % intensity of color of the bounded area patch. Default is % 0.2. % % orient: 'vert': add bounds in vertical (y) direction (default) % 'horiz': add bounds in horizontal (x) direction % % cmap: n x 3 colormap array. If included, lines will be colored % (in order of plotting) according to this colormap, % overriding any linespec or default colors. % % Output variables: % % hl: handles to line objects % % hp: handles to patch objects % % Example: % % x = linspace(0, 2*pi, 50); % y1 = sin(x); % y2 = cos(x); % e1 = rand(size(y1))*.5+.5; % e2 = [.25 .5]; % % ax(1) = subplot(2,2,1); % [l,p] = boundedline(x, y1, e1, '-b*', x, y2, e2, '--ro'); % outlinebounds(l,p); % title('Opaque bounds, with outline'); % % ax(2) = subplot(2,2,2); % boundedline(x, [y1;y2], rand(length(y1),2,2)*.5+.5, 'alpha'); % title('Transparent bounds'); % % ax(3) = subplot(2,2,3); % boundedline([y1;y2], x, e1(1), 'orientation', 'horiz') % title('Horizontal bounds'); % % ax(4) = subplot(2,2,4); % boundedline(x, repmat(y1, 4,1), permute(0.5:-0.1:0.2, [3 1 2]), ... % 'cmap', cool(4), 'transparency', 0.5); % title('Multiple bounds using colormap'); % Copyright 2010 Kelly Kearney %-------------------- % Parse input %-------------------- % Alpha flag isalpha = cellfun(@(x) ischar(x) && strcmp(x, 'alpha'), varargin); if any(isalpha) usealpha = true; varargin = varargin(~isalpha); else usealpha = false; end % Axis isax = cellfun(@(x) isscalar(x) && ishandle(x) && strcmp('axes', get(x,'type')), varargin); if any(isax) hax = varargin{isax}; varargin = varargin(~isax); else hax = gca; end % Transparency [found, trans, varargin] = parseparam(varargin, 'transparency'); if ~found trans = 0.2; end if ~isscalar(trans) || trans < 0 || trans > 1 error('Transparency must be scalar between 0 and 1'); end % Orientation [found, orient, varargin] = parseparam(varargin, 'orientation'); if ~found orient = 'vert'; end if strcmp(orient, 'vert') isvert = true; elseif strcmp(orient, 'horiz') isvert = false; else error('Orientation must be ''vert'' or ''horiz'''); end % Colormap [hascmap, cmap, varargin] = parseparam(varargin, 'cmap'); % X, Y, E triplets, and linespec [x,y,err,linespec] = deal(cell(0)); while ~isempty(varargin) if length(varargin) < 3 error('Unexpected input: should be x, y, bounds triplets'); end if all(cellfun(@isnumeric, varargin(1:3))) x = [x varargin(1)]; y = [y varargin(2)]; err = [err varargin(3)]; varargin(1:3) = []; else error('Unexpected input: should be x, y, bounds triplets'); end if ~isempty(varargin) && ischar(varargin{1}) linespec = [linespec varargin(1)]; varargin(1) = []; else linespec = [linespec {[]}]; end end %-------------------- % Reformat x and y % for line and patch % plotting %-------------------- % Calculate y values for bounding lines plotdata = cell(0,7); htemp = figure('visible', 'off'); for ix = 1:length(x) % Get full x, y, and linespec data for each line (easier to let plot % check for properly-sized x and y and expand values than to try to do % it myself) try if isempty(linespec{ix}) hltemp = plot(x{ix}, y{ix}); else hltemp = plot(x{ix}, y{ix}, linespec{ix}); end catch close(htemp); error('X and Y matrices and/or linespec not appropriate for line plot'); end linedata = get(hltemp, {'xdata', 'ydata', 'marker', 'linestyle', 'color'}); nline = size(linedata,1); % Expand bounds matrix if necessary if nline > 1 if ndims(err{ix}) == 3 err2 = squeeze(num2cell(err{ix},[1 2])); else err2 = repmat(err(ix),nline,1); end else err2 = err(ix); end % Figure out upper and lower bounds [lo, hi] = deal(cell(nline,1)); for iln = 1:nline x2 = linedata{iln,1}; y2 = linedata{iln,2}; nx = length(x2); if isvert lineval = y2; else lineval = x2; end sz = size(err2{iln}); if isequal(sz, [nx 2]) lo{iln} = lineval - err2{iln}(:,1)'; hi{iln} = lineval + err2{iln}(:,2)'; elseif isequal(sz, [nx 1]) lo{iln} = lineval - err2{iln}'; hi{iln} = lineval + err2{iln}'; elseif isequal(sz, [1 2]) lo{iln} = lineval - err2{iln}(1); hi{iln} = lineval + err2{iln}(2); elseif isequal(sz, [1 1]) lo{iln} = lineval - err2{iln}; hi{iln} = lineval + err2{iln}; elseif isequal(sz, [2 nx]) % not documented, but accepted anyways lo{iln} = lineval - err2{iln}(:,1); hi{iln} = lineval + err2{iln}(:,2); elseif isequal(sz, [1 nx]) % not documented, but accepted anyways lo{iln} = lineval - err2{iln}; hi{iln} = lineval + err2{iln}; elseif isequal(sz, [2 1]) % not documented, but accepted anyways lo{iln} = lineval - err2{iln}(1); hi{iln} = lineval + err2{iln}(2); else error('Error bounds must be npt x nside x nline array'); end end % Combine all data (xline, yline, marker, linestyle, color, lower bound % (x or y), upper bound (x or y) plotdata = [plotdata; linedata lo hi]; end close(htemp); % Override colormap if hascmap nd = size(plotdata,1); cmap = repmat(cmap, ceil(nd/size(cmap,1)), 1); cmap = cmap(1:nd,:); plotdata(:,5) = num2cell(cmap,2); end %-------------------- % Plot %-------------------- % Setup of x and y, plus line and patch properties nline = size(plotdata,1); [xl, yl, xp, yp, marker, lnsty, lncol, ptchcol, alpha] = deal(cell(nline,1)); for iln = 1:nline xl{iln} = plotdata{iln,1}; yl{iln} = plotdata{iln,2}; % if isvert % xp{iln} = [plotdata{iln,1} fliplr(plotdata{iln,1})]; % yp{iln} = [plotdata{iln,6} fliplr(plotdata{iln,7})]; % else % xp{iln} = [plotdata{iln,6} fliplr(plotdata{iln,7})]; % yp{iln} = [plotdata{iln,2} fliplr(plotdata{iln,2})]; % end [xp{iln}, yp{iln}] = calcpatch(plotdata{iln,1}, plotdata{iln,2}, isvert, plotdata{iln,6}, plotdata{iln,7}); marker{iln} = plotdata{iln,3}; lnsty{iln} = plotdata{iln,4}; if usealpha lncol{iln} = plotdata{iln,5}; ptchcol{iln} = plotdata{iln,5}; alpha{iln} = trans; else lncol{iln} = plotdata{iln,5}; ptchcol{iln} = interp1([0 1], [1 1 1; lncol{iln}], trans); alpha{iln} = 1; end end % Plot patches and lines if verLessThan('matlab', '8.4.0') [hp,hl] = deal(zeros(nline,1)); else [hp,hl] = deal(gobjects(nline,1)); end axes(hax); hold all; for iln = 1:nline hp(iln) = patch(xp{iln}, yp{iln}, ptchcol{iln}, 'facealpha', alpha{iln}, 'edgecolor', 'none'); end for iln = 1:nline hl(iln) = line(xl{iln}, yl{iln}, 'marker', marker{iln}, 'linestyle', lnsty{iln}, 'color', lncol{iln}); end %-------------------- % Assign output %-------------------- nargchk(0, 2, nargout); if nargout >= 1 varargout{1} = hl; end if nargout == 2 varargout{2} = hp; end %-------------------- % Parse optional % parameters %-------------------- function [found, val, vars] = parseparam(vars, param) isvar = cellfun(@(x) ischar(x) && strcmpi(x, param), vars); if sum(isvar) > 1 error('Parameters can only be passed once'); end if any(isvar) found = true; idx = find(isvar); val = vars{idx+1}; vars([idx idx+1]) = []; else found = false; val = []; end %---------------------------- % Calculate patch coordinates %---------------------------- function [xp, yp] = calcpatch(xl, yl, isvert, lo, hi) ismissing = any(isnan([xl;yl;lo;hi]),2); if isvert xp = [xl fliplr(xl)]; yp = [lo fliplr(hi)]; else xp = [lo fliplr(hi)]; yp = [yl fliplr(yl)]; end if any(ismissing) warning('NaNs in bounds; inpainting'); xp = inpaint_nans(xp'); yp = inpaint_nans(yp'); end