boundedline.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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(..., 'cmap', cmap)
  12. %
  13. % Input variables:
  14. %
  15. % x, y: x and y values, either vectors of the same length, matrices
  16. % of the same size, or vector/matrix pair where the row or
  17. % column size of the array matches the length of the vector
  18. % (same requirements as for plot function).
  19. %
  20. % b: npoint x nside x nline array. Distance from line to
  21. % boundary, for each point along the line (dimension 1), for
  22. % each side of the line (lower/upper or left/right, depending
  23. % on orientation) (dimension 2), and for each plotted line
  24. % described by the preceding x-y values (dimension 3). If
  25. % size(b,1) == 1, the bounds will be the same for all points
  26. % along the line. If size(b,2) == 1, the bounds will be
  27. % symmetrical on both sides of the lines. If size(b,3) == 1,
  28. % the same bounds will be applied to all lines described by
  29. % the preceding x-y arrays (only applicable when either x or
  30. % y is an array). Bounds cannot include Inf, -Inf, or NaN,
  31. %
  32. % linespec: line specification that determines line type, marker
  33. % symbol, and color of the plotted lines for the preceding
  34. % x-y values.
  35. %
  36. % 'alpha': if included, the bounded area will be rendered with a
  37. % partially-transparent patch the same color as the
  38. % corresponding line(s). If not included, the bounded area
  39. % will be an opaque patch with a lighter shade of the
  40. % corresponding line color.
  41. %
  42. % ax: handle of axis where lines will be plotted. If not
  43. % included, the current axis will be used.
  44. %
  45. % transp: Scalar between 0 and 1 indicating with the transparency or
  46. % intensity of color of the bounded area patch. Default is
  47. % 0.2.
  48. %
  49. % orient: 'vert': add bounds in vertical (y) direction (default)
  50. % 'horiz': add bounds in horizontal (x) direction
  51. %
  52. % cmap: n x 3 colormap array. If included, lines will be colored
  53. % (in order of plotting) according to this colormap,
  54. % overriding any linespec or default colors.
  55. %
  56. % Output variables:
  57. %
  58. % hl: handles to line objects
  59. %
  60. % hp: handles to patch objects
  61. %
  62. % Example:
  63. %
  64. % x = linspace(0, 2*pi, 50);
  65. % y1 = sin(x);
  66. % y2 = cos(x);
  67. % e1 = rand(size(y1))*.5+.5;
  68. % e2 = [.25 .5];
  69. %
  70. % ax(1) = subplot(2,2,1);
  71. % [l,p] = boundedline(x, y1, e1, '-b*', x, y2, e2, '--ro');
  72. % outlinebounds(l,p);
  73. % title('Opaque bounds, with outline');
  74. %
  75. % ax(2) = subplot(2,2,2);
  76. % boundedline(x, [y1;y2], rand(length(y1),2,2)*.5+.5, 'alpha');
  77. % title('Transparent bounds');
  78. %
  79. % ax(3) = subplot(2,2,3);
  80. % boundedline([y1;y2], x, e1(1), 'orientation', 'horiz')
  81. % title('Horizontal bounds');
  82. %
  83. % ax(4) = subplot(2,2,4);
  84. % boundedline(x, repmat(y1, 4,1), permute(0.5:-0.1:0.2, [3 1 2]), ...
  85. % 'cmap', cool(4), 'transparency', 0.5);
  86. % title('Multiple bounds using colormap');
  87. % Copyright 2010 Kelly Kearney
  88. %--------------------
  89. % Parse input
  90. %--------------------
  91. % Alpha flag
  92. isalpha = cellfun(@(x) ischar(x) && strcmp(x, 'alpha'), varargin);
  93. if any(isalpha)
  94. usealpha = true;
  95. varargin = varargin(~isalpha);
  96. else
  97. usealpha = false;
  98. end
  99. % Axis
  100. isax = cellfun(@(x) isscalar(x) && ishandle(x) && strcmp('axes', get(x,'type')), varargin);
  101. if any(isax)
  102. hax = varargin{isax};
  103. varargin = varargin(~isax);
  104. else
  105. hax = gca;
  106. end
  107. % Transparency
  108. [found, trans, varargin] = parseparam(varargin, 'transparency');
  109. if ~found
  110. trans = 0.2;
  111. end
  112. if ~isscalar(trans) || trans < 0 || trans > 1
  113. error('Transparency must be scalar between 0 and 1');
  114. end
  115. % Orientation
  116. [found, orient, varargin] = parseparam(varargin, 'orientation');
  117. if ~found
  118. orient = 'vert';
  119. end
  120. if strcmp(orient, 'vert')
  121. isvert = true;
  122. elseif strcmp(orient, 'horiz')
  123. isvert = false;
  124. else
  125. error('Orientation must be ''vert'' or ''horiz''');
  126. end
  127. % Colormap
  128. [hascmap, cmap, varargin] = parseparam(varargin, 'cmap');
  129. % X, Y, E triplets, and linespec
  130. [x,y,err,linespec] = deal(cell(0));
  131. while ~isempty(varargin)
  132. if length(varargin) < 3
  133. error('Unexpected input: should be x, y, bounds triplets');
  134. end
  135. if all(cellfun(@isnumeric, varargin(1:3)))
  136. x = [x varargin(1)];
  137. y = [y varargin(2)];
  138. err = [err varargin(3)];
  139. varargin(1:3) = [];
  140. else
  141. error('Unexpected input: should be x, y, bounds triplets');
  142. end
  143. if ~isempty(varargin) && ischar(varargin{1})
  144. linespec = [linespec varargin(1)];
  145. varargin(1) = [];
  146. else
  147. linespec = [linespec {[]}];
  148. end
  149. end
  150. %--------------------
  151. % Reformat x and y
  152. % for line and patch
  153. % plotting
  154. %--------------------
  155. % Calculate y values for bounding lines
  156. plotdata = cell(0,7);
  157. htemp = figure('visible', 'off');
  158. for ix = 1:length(x)
  159. % Get full x, y, and linespec data for each line (easier to let plot
  160. % check for properly-sized x and y and expand values than to try to do
  161. % it myself)
  162. try
  163. if isempty(linespec{ix})
  164. hltemp = plot(x{ix}, y{ix});
  165. else
  166. hltemp = plot(x{ix}, y{ix}, linespec{ix});
  167. end
  168. catch
  169. close(htemp);
  170. error('X and Y matrices and/or linespec not appropriate for line plot');
  171. end
  172. linedata = get(hltemp, {'xdata', 'ydata', 'marker', 'linestyle', 'color'});
  173. nline = size(linedata,1);
  174. % Expand bounds matrix if necessary
  175. if nline > 1
  176. if ndims(err{ix}) == 3
  177. err2 = squeeze(num2cell(err{ix},[1 2]));
  178. else
  179. err2 = repmat(err(ix),nline,1);
  180. end
  181. else
  182. err2 = err(ix);
  183. end
  184. % Figure out upper and lower bounds
  185. [lo, hi] = deal(cell(nline,1));
  186. for iln = 1:nline
  187. x2 = linedata{iln,1};
  188. y2 = linedata{iln,2};
  189. nx = length(x2);
  190. if isvert
  191. lineval = y2;
  192. else
  193. lineval = x2;
  194. end
  195. sz = size(err2{iln});
  196. if isequal(sz, [nx 2])
  197. lo{iln} = lineval - err2{iln}(:,1)';
  198. hi{iln} = lineval + err2{iln}(:,2)';
  199. elseif isequal(sz, [nx 1])
  200. lo{iln} = lineval - err2{iln}';
  201. hi{iln} = lineval + err2{iln}';
  202. elseif isequal(sz, [1 2])
  203. lo{iln} = lineval - err2{iln}(1);
  204. hi{iln} = lineval + err2{iln}(2);
  205. elseif isequal(sz, [1 1])
  206. lo{iln} = lineval - err2{iln};
  207. hi{iln} = lineval + err2{iln};
  208. elseif isequal(sz, [2 nx]) % not documented, but accepted anyways
  209. lo{iln} = lineval - err2{iln}(:,1);
  210. hi{iln} = lineval + err2{iln}(:,2);
  211. elseif isequal(sz, [1 nx]) % not documented, but accepted anyways
  212. lo{iln} = lineval - err2{iln};
  213. hi{iln} = lineval + err2{iln};
  214. elseif isequal(sz, [2 1]) % not documented, but accepted anyways
  215. lo{iln} = lineval - err2{iln}(1);
  216. hi{iln} = lineval + err2{iln}(2);
  217. else
  218. error('Error bounds must be npt x nside x nline array');
  219. end
  220. end
  221. % Combine all data (xline, yline, marker, linestyle, color, lower bound
  222. % (x or y), upper bound (x or y)
  223. plotdata = [plotdata; linedata lo hi];
  224. end
  225. close(htemp);
  226. % Override colormap
  227. if hascmap
  228. nd = size(plotdata,1);
  229. cmap = repmat(cmap, ceil(nd/size(cmap,1)), 1);
  230. cmap = cmap(1:nd,:);
  231. plotdata(:,5) = num2cell(cmap,2);
  232. end
  233. %--------------------
  234. % Plot
  235. %--------------------
  236. % Setup of x and y, plus line and patch properties
  237. nline = size(plotdata,1);
  238. [xl, yl, xp, yp, marker, lnsty, lncol, ptchcol, alpha] = deal(cell(nline,1));
  239. for iln = 1:nline
  240. xl{iln} = plotdata{iln,1};
  241. yl{iln} = plotdata{iln,2};
  242. % if isvert
  243. % xp{iln} = [plotdata{iln,1} fliplr(plotdata{iln,1})];
  244. % yp{iln} = [plotdata{iln,6} fliplr(plotdata{iln,7})];
  245. % else
  246. % xp{iln} = [plotdata{iln,6} fliplr(plotdata{iln,7})];
  247. % yp{iln} = [plotdata{iln,2} fliplr(plotdata{iln,2})];
  248. % end
  249. [xp{iln}, yp{iln}] = calcpatch(plotdata{iln,1}, plotdata{iln,2}, isvert, plotdata{iln,6}, plotdata{iln,7});
  250. marker{iln} = plotdata{iln,3};
  251. lnsty{iln} = plotdata{iln,4};
  252. if usealpha
  253. lncol{iln} = plotdata{iln,5};
  254. ptchcol{iln} = plotdata{iln,5};
  255. alpha{iln} = trans;
  256. else
  257. lncol{iln} = plotdata{iln,5};
  258. ptchcol{iln} = interp1([0 1], [1 1 1; lncol{iln}], trans);
  259. alpha{iln} = 1;
  260. end
  261. end
  262. % Plot patches and lines
  263. if verLessThan('matlab', '8.4.0')
  264. [hp,hl] = deal(zeros(nline,1));
  265. else
  266. [hp,hl] = deal(gobjects(nline,1));
  267. end
  268. axes(hax);
  269. hold all;
  270. for iln = 1:nline
  271. hp(iln) = patch(xp{iln}, yp{iln}, ptchcol{iln}, 'facealpha', alpha{iln}, 'edgecolor', 'none');
  272. end
  273. for iln = 1:nline
  274. hl(iln) = line(xl{iln}, yl{iln}, 'marker', marker{iln}, 'linestyle', lnsty{iln}, 'color', lncol{iln});
  275. end
  276. %--------------------
  277. % Assign output
  278. %--------------------
  279. nargchk(0, 2, nargout);
  280. if nargout >= 1
  281. varargout{1} = hl;
  282. end
  283. if nargout == 2
  284. varargout{2} = hp;
  285. end
  286. %--------------------
  287. % Parse optional
  288. % parameters
  289. %--------------------
  290. function [found, val, vars] = parseparam(vars, param)
  291. isvar = cellfun(@(x) ischar(x) && strcmpi(x, param), vars);
  292. if sum(isvar) > 1
  293. error('Parameters can only be passed once');
  294. end
  295. if any(isvar)
  296. found = true;
  297. idx = find(isvar);
  298. val = vars{idx+1};
  299. vars([idx idx+1]) = [];
  300. else
  301. found = false;
  302. val = [];
  303. end
  304. %----------------------------
  305. % Calculate patch coordinates
  306. %----------------------------
  307. function [xp, yp] = calcpatch(xl, yl, isvert, lo, hi)
  308. ismissing = any(isnan([xl;yl;lo;hi]),2);
  309. if isvert
  310. xp = [xl fliplr(xl)];
  311. yp = [lo fliplr(hi)];
  312. else
  313. xp = [lo fliplr(hi)];
  314. yp = [yl fliplr(yl)];
  315. end
  316. if any(ismissing)
  317. warning('NaNs in bounds; inpainting');
  318. xp = inpaint_nans(xp');
  319. yp = inpaint_nans(yp');
  320. end