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