a b/Supporting Functions/kakearney-boundedline-pkg-8179f9a/boundedline/boundedline.m
1
function varargout = boundedline(varargin)
2
%BOUNDEDLINE Plot a line with shaded error/confidence bounds
3
%
4
% [hl, hp] = boundedline(x, y, b)
5
% [hl, hp] = boundedline(x, y, b, linespec)
6
% [hl, hp] = boundedline(x1, y1, b1, linespec1,  x2, y2, b2, linespec2)
7
% [hl, hp] = boundedline(..., 'alpha')
8
% [hl, hp] = boundedline(..., ax)
9
% [hl, hp] = boundedline(..., 'transparency', trans)
10
% [hl, hp] = boundedline(..., 'orientation', orient)
11
% [hl, hp] = boundedline(..., 'nan', nanflag)
12
% [hl, hp] = boundedline(..., 'cmap', cmap)
13
%
14
% Input variables:
15
%
16
%   x, y:       x and y values, either vectors of the same length, matrices
17
%               of the same size, or vector/matrix pair where the row or
18
%               column size of the array matches the length of the vector
19
%               (same requirements as for plot function).
20
%
21
%   b:          npoint x nside x nline array.  Distance from line to
22
%               boundary, for each point along the line (dimension 1), for
23
%               each side of the line (lower/upper or left/right, depending
24
%               on orientation) (dimension 2), and for each plotted line
25
%               described by the preceding x-y values (dimension 3).  If
26
%               size(b,1) == 1, the bounds will be the same for all points
27
%               along the line.  If size(b,2) == 1, the bounds will be
28
%               symmetrical on both sides of the lines.  If size(b,3) == 1,
29
%               the same bounds will be applied to all lines described by
30
%               the preceding x-y arrays (only applicable when either x or
31
%               y is an array).  Bounds cannot include Inf, -Inf, or NaN,
32
%
33
%   linespec:   line specification that determines line type, marker
34
%               symbol, and color of the plotted lines for the preceding
35
%               x-y values.
36
%
37
%   'alpha':    if included, the bounded area will be rendered with a
38
%               partially-transparent patch the same color as the
39
%               corresponding line(s).  If not included, the bounded area
40
%               will be an opaque patch with a lighter shade of the
41
%               corresponding line color.
42
%
43
%   ax:         handle of axis where lines will be plotted.  If not
44
%               included, the current axis will be used.
45
%
46
%   transp:     Scalar between 0 and 1 indicating with the transparency or
47
%               intensity of color of the bounded area patch. Default is
48
%               0.2.
49
%
50
%   orient:     direction to add bounds
51
%               'vert':   add bounds in vertical (y) direction (default)
52
%               'horiz':  add bounds in horizontal (x) direction 
53
%
54
%   nanflag:    Sets how NaNs in the boundedline patch should be handled
55
%               'fill':   fill the value based on neighboring values,
56
%                         smoothing over the gap
57
%               'gap':    leave a blank space over/below the line
58
%               'remove': drop NaNs from patches, creating a linear
59
%                         interpolation over the gap.  Note that this
60
%                         applies only to the bounds; NaNs in the line will
61
%                         remain.
62
%
63
%   cmap:       n x 3 colormap array.  If included, lines will be colored
64
%               (in order of plotting) according to this colormap,
65
%               overriding any linespec or default colors. 
66
%
67
% Output variables:
68
%
69
%   hl:         handles to line objects
70
%
71
%   hp:         handles to patch objects
72
%
73
% Example:
74
%
75
% x = linspace(0, 2*pi, 50);
76
% y1 = sin(x);
77
% y2 = cos(x);
78
% e1 = rand(size(y1))*.5+.5;
79
% e2 = [.25 .5];
80
% 
81
% ax(1) = subplot(2,2,1);
82
% [l,p] = boundedline(x, y1, e1, '-b*', x, y2, e2, '--ro');
83
% outlinebounds(l,p);
84
% title('Opaque bounds, with outline');
85
% 
86
% ax(2) = subplot(2,2,2);
87
% boundedline(x, [y1;y2], rand(length(y1),2,2)*.5+.5, 'alpha');
88
% title('Transparent bounds');
89
% 
90
% ax(3) = subplot(2,2,3);
91
% boundedline([y1;y2], x, e1(1), 'orientation', 'horiz')
92
% title('Horizontal bounds');
93
% 
94
% ax(4) = subplot(2,2,4);
95
% boundedline(x, repmat(y1, 4,1), permute(0.5:-0.1:0.2, [3 1 2]), ...
96
%             'cmap', cool(4), 'transparency', 0.5);
97
% title('Multiple bounds using colormap');
98
99
100
% Copyright 2010 Kelly Kearney
101
102
%--------------------
103
% Parse input
104
%--------------------
105
106
% Alpha flag
107
108
isalpha = cellfun(@(x) ischar(x) && strcmp(x, 'alpha'), varargin);
109
if any(isalpha)
110
    usealpha = true;
111
    varargin = varargin(~isalpha);
112
else
113
    usealpha = false;
114
end
115
116
% Axis
117
118
isax = cellfun(@(x) isscalar(x) && ishandle(x) && strcmp('axes', get(x,'type')), varargin);
119
if any(isax)
120
    hax = varargin{isax};
121
    varargin = varargin(~isax);
122
else
123
    hax = gca;
124
end
125
126
% Transparency
127
128
[found, trans, varargin] = parseparam(varargin, 'transparency');
129
130
if ~found
131
    trans = 0.2;
132
end
133
134
if ~isscalar(trans) || trans < 0 || trans > 1
135
    error('Transparency must be scalar between 0 and 1');
136
end
137
138
% Orientation
139
140
[found, orient, varargin] = parseparam(varargin, 'orientation');
141
142
if ~found
143
    orient = 'vert';
144
end
145
146
if strcmp(orient, 'vert')
147
    isvert = true;
148
elseif strcmp(orient, 'horiz')
149
    isvert = false;
150
else
151
    error('Orientation must be ''vert'' or ''horiz''');
152
end
153
154
155
% Colormap
156
157
[hascmap, cmap, varargin] = parseparam(varargin, 'cmap');
158
159
160
% NaN flag
161
162
[found, nanflag, varargin] = parseparam(varargin, 'nan');
163
if ~found
164
    nanflag = 'fill';
165
end
166
if ~ismember(nanflag, {'fill', 'gap', 'remove'})
167
    error('Nan flag must be ''fill'', ''gap'', or ''remove''');
168
end
169
170
% X, Y, E triplets, and linespec
171
172
[x,y,err,linespec] = deal(cell(0));
173
while ~isempty(varargin)
174
    if length(varargin) < 3
175
        error('Unexpected input: should be x, y, bounds triplets');
176
    end
177
    if all(cellfun(@isnumeric, varargin(1:3)))
178
        x = [x varargin(1)];
179
        y = [y varargin(2)];
180
        err = [err varargin(3)];
181
        varargin(1:3) = [];
182
    else
183
        error('Unexpected input: should be x, y, bounds triplets');
184
    end
185
    if ~isempty(varargin) && ischar(varargin{1})
186
        linespec = [linespec varargin(1)];
187
        varargin(1) = [];
188
    else
189
        linespec = [linespec {[]}];
190
    end 
191
end    
192
193
%--------------------
194
% Reformat x and y
195
% for line and patch
196
% plotting
197
%--------------------
198
199
% Calculate y values for bounding lines
200
201
plotdata = cell(0,7);
202
203
htemp = figure('visible', 'off');
204
for ix = 1:length(x)
205
    
206
    % Get full x, y, and linespec data for each line (easier to let plot
207
    % check for properly-sized x and y and expand values than to try to do
208
    % it myself) 
209
    
210
    try
211
        if isempty(linespec{ix})
212
            hltemp = plot(x{ix}, y{ix});
213
        else
214
            hltemp = plot(x{ix}, y{ix}, linespec{ix});
215
        end
216
    catch
217
        close(htemp);
218
        error('X and Y matrices and/or linespec not appropriate for line plot');
219
    end
220
    
221
    linedata = get(hltemp, {'xdata', 'ydata', 'marker', 'linestyle', 'color'});
222
    
223
    nline = size(linedata,1);
224
    
225
    % Expand bounds matrix if necessary
226
    
227
    if nline > 1
228
        if ndims(err{ix}) == 3
229
            err2 = squeeze(num2cell(err{ix},[1 2]));
230
        else
231
            err2 = repmat(err(ix),nline,1);
232
        end
233
    else
234
        err2 = err(ix);
235
    end
236
    
237
    % Figure out upper and lower bounds
238
    
239
    [lo, hi] = deal(cell(nline,1));
240
    for iln = 1:nline
241
        
242
        x2 = linedata{iln,1};
243
        y2 = linedata{iln,2};
244
        nx = length(x2);
245
        
246
        if isvert
247
            lineval = y2;
248
        else
249
            lineval = x2;
250
        end
251
            
252
        sz = size(err2{iln});
253
        
254
        if isequal(sz, [nx 2])
255
            lo{iln} = lineval - err2{iln}(:,1)';
256
            hi{iln} = lineval + err2{iln}(:,2)';
257
        elseif isequal(sz, [nx 1])
258
            lo{iln} = lineval - err2{iln}';
259
            hi{iln} = lineval + err2{iln}';
260
        elseif isequal(sz, [1 2])
261
            lo{iln} = lineval - err2{iln}(1);
262
            hi{iln} = lineval + err2{iln}(2);
263
        elseif isequal(sz, [1 1])
264
            lo{iln} = lineval - err2{iln};
265
            hi{iln} = lineval + err2{iln};
266
        elseif isequal(sz, [2 nx]) % not documented, but accepted anyways
267
            lo{iln} = lineval - err2{iln}(:,1);
268
            hi{iln} = lineval + err2{iln}(:,2);
269
        elseif isequal(sz, [1 nx]) % not documented, but accepted anyways
270
            lo{iln} = lineval - err2{iln};
271
            hi{iln} = lineval + err2{iln};
272
        elseif isequal(sz, [2 1]) % not documented, but accepted anyways
273
            lo{iln} = lineval - err2{iln}(1);
274
            hi{iln} = lineval + err2{iln}(2);
275
        else
276
            error('Error bounds must be npt x nside x nline array');
277
        end 
278
            
279
    end
280
    
281
    % Combine all data (xline, yline, marker, linestyle, color, lower bound
282
    % (x or y), upper bound (x or y) 
283
    
284
    plotdata = [plotdata; linedata lo hi];
285
        
286
end
287
close(htemp);
288
289
% Override colormap
290
291
if hascmap
292
    nd = size(plotdata,1);
293
    cmap = repmat(cmap, ceil(nd/size(cmap,1)), 1);
294
    cmap = cmap(1:nd,:);
295
    plotdata(:,5) = num2cell(cmap,2);
296
end
297
298
299
%--------------------
300
% Plot
301
%--------------------
302
303
% Setup of x and y, plus line and patch properties
304
305
nline = size(plotdata,1);
306
[xl, yl, xp, yp, marker, lnsty, lncol, ptchcol, alpha] = deal(cell(nline,1));
307
308
for iln = 1:nline
309
    xl{iln} = plotdata{iln,1};
310
    yl{iln} = plotdata{iln,2};
311
%     if isvert
312
%         xp{iln} = [plotdata{iln,1} fliplr(plotdata{iln,1})];
313
%         yp{iln} = [plotdata{iln,6} fliplr(plotdata{iln,7})];
314
%     else
315
%         xp{iln} = [plotdata{iln,6} fliplr(plotdata{iln,7})];
316
%         yp{iln} = [plotdata{iln,2} fliplr(plotdata{iln,2})];
317
%     end
318
    
319
    [xp{iln}, yp{iln}] = calcpatch(plotdata{iln,1}, plotdata{iln,2}, isvert, plotdata{iln,6}, plotdata{iln,7}, nanflag);
320
    
321
    marker{iln} = plotdata{iln,3};
322
    lnsty{iln} = plotdata{iln,4};
323
    
324
    if usealpha
325
        lncol{iln} = plotdata{iln,5};
326
        ptchcol{iln} = plotdata{iln,5};
327
        alpha{iln} = trans;
328
    else
329
        lncol{iln} = plotdata{iln,5};
330
        ptchcol{iln} = interp1([0 1], [1 1 1; lncol{iln}], trans);
331
        alpha{iln} = 1;
332
    end
333
end
334
    
335
% Plot patches and lines
336
337
if verLessThan('matlab', '8.4.0')
338
    [hp,hl] = deal(zeros(nline,1));
339
else
340
    [hp,hl] = deal(gobjects(nline,1));
341
end
342
343
344
for iln = 1:nline
345
    hp(iln) = patch(xp{iln}, yp{iln}, ptchcol{iln}, 'facealpha', alpha{iln}, 'edgecolor', 'none', 'parent', hax);
346
end
347
348
for iln = 1:nline
349
    hl(iln) = line(xl{iln}, yl{iln}, 'marker', marker{iln}, 'linestyle', lnsty{iln}, 'color', lncol{iln}, 'parent', hax);
350
end
351
352
%--------------------
353
% Assign output
354
%--------------------
355
356
nargoutchk(0,2);
357
358
if nargout >= 1
359
    varargout{1} = hl;
360
end
361
362
if nargout == 2
363
    varargout{2} = hp;
364
end
365
366
%--------------------
367
% Parse optional 
368
% parameters
369
%--------------------
370
371
function [found, val, vars] = parseparam(vars, param)
372
373
isvar = cellfun(@(x) ischar(x) && strcmpi(x, param), vars);
374
375
if sum(isvar) > 1
376
    error('Parameters can only be passed once');
377
end
378
379
if any(isvar)
380
    found = true;
381
    idx = find(isvar);
382
    val = vars{idx+1};
383
    vars([idx idx+1]) = [];
384
else
385
    found = false;
386
    val = [];
387
end
388
389
%----------------------------
390
% Calculate patch coordinates
391
%----------------------------
392
393
function [xp, yp] = calcpatch(xl, yl, isvert, lo, hi, nanflag)
394
395
ismissing = isnan([xl;yl;lo;hi]);
396
397
% If gap method, split
398
399
if any(ismissing(:)) && strcmp(nanflag, 'gap')
400
    
401
    tmp = [xl;yl;lo;hi];
402
   
403
    idx = find(any(ismissing,1));
404
    n = diff([0 idx length(xl)]);
405
    
406
    tmp = mat2cell(tmp, 4, n);
407
    isemp = cellfun('isempty', tmp);
408
    tmp = tmp(~isemp);
409
    
410
    tmp = cellfun(@(a) a(:,~any(isnan(a),1)), tmp, 'uni', 0);
411
    isemp = cellfun('isempty', tmp);
412
    tmp = tmp(~isemp);
413
    
414
    xl = cellfun(@(a) a(1,:), tmp, 'uni', 0);
415
    yl = cellfun(@(a) a(2,:), tmp, 'uni', 0);
416
    lo = cellfun(@(a) a(3,:), tmp, 'uni', 0);
417
    hi = cellfun(@(a) a(4,:), tmp, 'uni', 0);
418
else
419
    xl = {xl};
420
    yl = {yl};
421
    lo = {lo};
422
    hi = {hi};
423
end
424
425
[xp, yp] = deal(cell(size(xl)));
426
427
for ii = 1:length(xl)
428
429
    iseq = ~verLessThan('matlab', '8.4.0') && isequal(lo{ii}, hi{ii}); % deal with zero-width bug in R2014b/R2015a
430
431
    if isvert
432
        if iseq
433
            xp{ii} = [xl{ii} nan(size(xl{ii}))];
434
            yp{ii} = [lo{ii} fliplr(hi{ii})];
435
        else
436
            xp{ii} = [xl{ii} fliplr(xl{ii})];
437
            yp{ii} = [lo{ii} fliplr(hi{ii})];
438
        end
439
    else
440
        if iseq
441
            xp{ii} = [lo{ii} fliplr(hi{ii})];
442
            yp{ii} = [yl{ii} nan(size(yl{ii}))];
443
        else
444
            xp{ii} = [lo{ii} fliplr(hi{ii})];
445
            yp{ii} = [yl{ii} fliplr(yl{ii})];
446
        end
447
    end
448
    
449
    if strcmp(nanflag, 'fill')
450
        xp{ii} = inpaint_nans(xp{ii}', 4);
451
        yp{ii} = inpaint_nans(yp{ii}', 4);
452
        if iseq % need to maintain NaNs for zero-width bug
453
            nx = length(xp{ii});
454
            xp{ii}((nx/2)+1:end) = NaN;
455
        end
456
    elseif strcmp(nanflag, 'remove')
457
        if iseq
458
            nx = length(xp{ii});
459
            keepnan = false(size(xp));
460
            keepnan((nx/2)+1:end) = true;
461
            isn = (isnan(xp{ii}) | isnan(yp{ii})) & ~keepnan;
462
        else
463
            isn = isnan(xp{ii}) | isnan(yp{ii});
464
        end
465
        xp{ii} = xp{ii}(~isn);
466
        yp{ii} = yp{ii}(~isn);
467
    end
468
    
469
end
470
471
if strcmp(nanflag, 'gap')
472
    [xp, yp] = singlepatch(xp, yp);
473
else
474
    xp = xp{1};
475
    yp = yp{1};
476
end
477