heatmap_AD.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. function [hImage, hText, hXText, p] = heatmap_AD(mat, xlab, ylab, textmat, varargin)
  2. % HEATMAP displays a matrix as a heatmap image
  3. %
  4. % USAGE:
  5. % [hImage, hText, hTick] = heatmap(matrix, xlabels, ylabels, textmatrix, 'param', value, ...)
  6. %
  7. % INPUTS:
  8. % * HEATMAP displays "matrix" as an image whose color intensities reflect
  9. % the magnitude of the values in "matrix".
  10. %
  11. % * "xlabels" (and "ylabels") can be either a numeric vector or cell array
  12. % of strings that represent the columns (or rows) of the matrix. If either
  13. % is not specified or empty, no labels will be drawn.
  14. %
  15. % * "textmat" can either be: 1 (or true), in which case the "matrix" values will be
  16. % displayed in each square, a format string, in which case the matrix
  17. % values will be displayed formatted according to the string specified, a numeric
  18. % matrix the size of "matrix", in which case those values will be displayed as
  19. % strings or a cell matrix of strings the size of "matrix", in which case each
  20. % string will be displayed. If not specified or empty, no text will be
  21. % displayed on the image
  22. %
  23. % OTHER PARAMETERS (passed as parameter-value pairs)
  24. % * 'Colormap': Either a matrix of size numLevels-by-3 representing the
  25. % colormap to be used or a string or function handle representing a
  26. % function that returns a colormap, example, 'jet', 'hsv' or @cool.
  27. % Non-standard colormaps available within HEATMAP include 'money' and 'red'.
  28. % By default, the current figure's colormap is used.
  29. %
  30. % * 'ColorLevels': The number of distinct levels in the colormap (default:
  31. % 64). If more levels are specified than are present in the colormap, the
  32. % levels in the colormap are interpolated. If fewer are specified the
  33. % colormap is downsampled.
  34. %
  35. % * 'UseLogColormap': A true/false value which, if true, specifies that the
  36. % intensities displayed should match the log of the "matrix" values. Use
  37. % this if the data is naturally on a logarithmic scale (default: false)
  38. %
  39. % * 'UseFigureColormap': Specifies whether the figure's colormap should be
  40. % used. If false, the color intensities after applying the
  41. % specified/default colormap will be hardcoded, so that the image will be
  42. % independent of the figure's colormap. If this option is true, the figure
  43. % colormap in the end will be replaced by specified/default colormap.
  44. % (default = true)
  45. %
  46. % * 'NaNColor': A 3-element [R G B] vector specifying the color used to display NaN
  47. % or missing value. [0 0 0] corresponds to black and [1 1 1] to white. By
  48. % default MATLAB displays NaN values using the color assigned to the
  49. % lowest value in the colormap. Specifying this option automatically sets
  50. % the 'UseFigureColormap' option to false because the color mapping must
  51. % be computed prior to setting the nan color.
  52. %
  53. % * 'MinColorValue': A scalar number corresponding to the value of the data
  54. % that is mapped to the lowest color of the colormap. By default this is
  55. % the minimum value of the matrix input.
  56. %
  57. % * 'MaxColorValue': A scalar number corresponding to the value of the data
  58. % that is mapped to the highest color of the colormap. By default this is
  59. % the maximum value of the matrix input.
  60. %
  61. % * 'Parent': Handle to an axes object
  62. %
  63. % * 'TextColor': Either a color specification of all the text displayed on
  64. % the image or a string 'xor' which sets the EraseMode property of the text
  65. % objects to 'xor'. This will display all the text labels in a color that
  66. % contrasts its background.
  67. %
  68. % * 'FontSize': The initial fontSize of the text labels on the image. As
  69. % the image size is scaled the fontSize is shrunk appropriately.
  70. %
  71. % * 'ColorBar': Display colorbar. The corresponding value parameter should
  72. % be either logical 1 or 0 or a cell array of any additional parameters
  73. % you wish to pass to the colorbar function (such as location)
  74. %
  75. % * 'GridLines': Draw grid lines separating adjacent sections of the
  76. % heatmap. The value of the parameter is a LineStyle specification, for example,
  77. % :, -, -. or --. By default, no grid lines are drawn.
  78. %
  79. % * 'TickAngle': Angle of rotation of tick labels on x-axis. (Default: 0)
  80. %
  81. % * 'ShowAllTicks': Set to 1 or true to force all ticks and labels to be
  82. % drawn. This can make the axes labels look crowded. (Default: false)
  83. %
  84. % * 'TickFontSize': Font size of the X and Y tick labels. Default value is
  85. % the default axes font size, usually 10. Set to a lower value if many
  86. % tick labels are being displayed
  87. %
  88. % * 'TickTexInterpreter': Set to 1 or true to render tick labels using a TEX
  89. % interpreter. For example, '_b' and '^o' would be rendered as subscript
  90. % b and the degree symbol with the TEX interpreter. This parameter is only
  91. % available in MATLAB R2014b and above (Default: false)
  92. %
  93. % OUTPUTS:
  94. % * hImage: handle to the image object
  95. % * hText : handle to the text objects (empty if no text labels are drawn)
  96. % * hTick : handle to the X-tick label text objects if tick angle is not 0
  97. % (empty otherwise)
  98. %
  99. % Notes:
  100. % * The 'money' colormap displays a colormap where 0 values are mapped to
  101. % white, negative values displayed in varying shades of red and positive
  102. % values in varying shades of green
  103. % * The 'red' colormap maps 0 values to white and higher values to red
  104. %
  105. % EXAMPLES:
  106. % data = reshape(sort(randi(100,10)),10,10)-50;
  107. % heatmap(data, cellstr(('A':'J')'), mean(data,2), '%0.0f%%',...
  108. % 'Colormap', 'money', 'Colorbar', true, 'GridLines', ':',...
  109. % 'TextColor', 'b')
  110. % For detailed examples, see the associated document heatmap_examples.m
  111. % Copyright The MathWorks, Inc. 2009-2014
  112. % Handle missing inputs
  113. if nargin < 1, error('Heatmap requires at least one input argument'); end
  114. if nargin < 2, xlab = []; end
  115. if nargin < 3, ylab = []; end
  116. if nargin < 4, textmat = []; end
  117. % Parse parameter/value inputs
  118. p = parseInputs(mat, varargin{:});
  119. % Get heatmap axes information if it already exists
  120. p.axesInfo = getHeatmapAxesInfo(p.hAxes);
  121. % Calculate the colormap based on inputs
  122. p = calculateColormap(p, mat);
  123. % Create heatmap image
  124. p = plotHeatmap(p, mat); % New properties hImage and cdata added
  125. % Generate grid lines if selected
  126. generateGridLines(p);
  127. % Set axes labels
  128. [p, xlab, ylab, hXText, origPos] = setAxesTickLabels(p, xlab, ylab);
  129. % Set text labels
  130. [p, displayText, fontScaleFactor] = setTextLabels(p, mat, textmat);
  131. % Add colorbar if selected
  132. addColorbar(p, mat, textmat)
  133. % Store heatmap properties in axes for callbacks
  134. axesInfo = struct('Type', 'heatmap', 'Parameters', p, 'FontScaleFactor', ...
  135. fontScaleFactor, 'mat', mat, 'hXText', hXText, ...
  136. 'origAxesPos', origPos);
  137. axesInfo.xlab = xlab;
  138. axesInfo.ylab = ylab;
  139. axesInfo.displayText = displayText;
  140. set(p.hAxes, 'UserData', axesInfo);
  141. % Define callbacks
  142. dObj = datacursormode(p.hFig);
  143. set(dObj, 'Updatefcn', @cursorFun);
  144. zObj = zoom(p.hFig);
  145. set(zObj, 'ActionPostCallback', @(obj,evd)updateLabels(evd.Axes,true));
  146. pObj = pan(p.hFig);
  147. % set(pObj, 'ActionPreCallback', @prePan);
  148. set(pObj, 'ActionPostCallback', @(obj,evd)updateLabels(evd.Axes,true));
  149. set(p.hFig, 'ResizeFcn', @resize)
  150. % Set outputs
  151. hImage = p.hImage;
  152. hText = p.hText;
  153. end
  154. % ---------------------- Heatmap Creation Functions ----------------------
  155. % Parse PV inputs & return structure of parameters
  156. function param = parseInputs(mat, varargin)
  157. p = inputParser;
  158. p.addParamValue('Colormap',[]); %#ok<*NVREPL>
  159. p.addParamValue('ColorLevels',[]);
  160. p.addParamValue('TextColor',[0 0 0]);
  161. p.addParamValue('UseFigureColormap',true);
  162. p.addParamValue('UseLogColormap',false);
  163. p.addParamValue('Parent',NaN);
  164. p.addParamValue('FontSize',[]);
  165. p.addParamValue('Colorbar',[]);
  166. p.addParamValue('GridLines','none');
  167. p.addParamValue('TickAngle',0);
  168. p.addParamValue('ShowAllTicks',false);
  169. p.addParamValue('TickFontSize',[]);
  170. p.addParamValue('TickTexInterpreter',false);
  171. p.addParamValue('NaNColor', [NaN NaN NaN], @(x)isnumeric(x) && length(x)==3 && all(x>=0) && all(x<=1));
  172. p.addParamValue('MinColorValue', nan, @(x)isnumeric(x) && isscalar(x));
  173. p.addParamValue('MaxColorValue', nan, @(x)isnumeric(x) && isscalar(x));
  174. p.parse(varargin{:});
  175. param = p.Results;
  176. if ~ishandle(param.Parent) || ~strcmp(get(param.Parent,'type'), 'axes')
  177. param.Parent = gca;
  178. end
  179. ind = ~isinf(mat(:)) | isnan(mat(:));
  180. if isnan(param.MinColorValue)
  181. param.MinColorValue = min(mat(ind));
  182. end
  183. if isnan(param.MaxColorValue)
  184. param.MaxColorValue = max(mat(ind));
  185. end
  186. % Add a few other parameters
  187. param.hAxes = param.Parent;
  188. param.hFig = ancestor(param.hAxes, 'figure');
  189. param.IsGraphics2 = ~verLessThan('matlab','8.4');
  190. param.ExplicitlyComputeImage = ~all(isnan(param.NaNColor)) ... NaNColor is specified
  191. || ~param.IsGraphics2 && ~param.UseFigureColormap;
  192. % if param.IsGraphics2 && ~param.UseFigureColormap && ~isempty(param.ColorBar) % graphics v2
  193. % warning('heatmap:graphics2figurecolormap', 'The UseFigureColormap false option with colorbar is not supported in versions R2014b and above. In most such cases UseFigureColormap false is unnecessary');
  194. % end
  195. end
  196. % Visualize heatmap image
  197. function p = plotHeatmap(p, mat)
  198. p.cdata = [];
  199. if p.UseLogColormap
  200. p.Colormap = resamplecmap(p.Colormap, p.ColorLevels, ...
  201. logspace(0,log10(p.ColorLevels),p.ColorLevels));
  202. end
  203. if p.ExplicitlyComputeImage
  204. % Calculate the color data explicitly and then display it as an image.
  205. n = p.MinColorValue;
  206. x = p.MaxColorValue;
  207. if x == n, x = n+1; end
  208. p.cdata = round((mat-n)/(x-n)*(p.ColorLevels-1)+1);
  209. %p.cdata = ceil((mat-n)/(x-n)*p.ColorLevels);
  210. p.cdata(p.cdata<1) = 1; % Clipping
  211. p.cdata(p.cdata>p.ColorLevels) = p.ColorLevels; % Clipping
  212. nanInd = find(isnan(p.cdata));
  213. p.cdata(isnan(p.cdata)) = 1;
  214. p.cdata = reshape(p.Colormap(p.cdata(:),:),[size(p.cdata) 3]);
  215. % Handle NaNColor case
  216. if ~all(isnan(p.NaNColor))
  217. p.cdata(nanInd ) = p.NaNColor(1); % Set red color level of nan indices
  218. p.cdata(nanInd + numel(p.cdata)/3) = p.NaNColor(2); % Set green color level of nan indices
  219. p.cdata(nanInd + 2*numel(p.cdata)/3) = p.NaNColor(3); % set blue color level of nan indices
  220. end
  221. % Add a small dummy image so that colorbar subsequently works
  222. [indr, indc] = find(~isnan(mat),1);
  223. imagesc(indr, indc, mat(indr,indc),'Parent',p.hAxes);
  224. nextplot = get(p.hAxes,'nextplot');
  225. set(p.hAxes,'nextplot','add');
  226. p.hImage = image(p.cdata, 'Parent', p.hAxes);
  227. set(p.hAxes,'nextplot',nextplot);
  228. axis(p.hAxes,'tight');
  229. else
  230. % Use a scaled image plot. Axes CLims and colormap will be set later
  231. p.hImage = imagesc(mat, 'Parent', p.hAxes);
  232. end
  233. set(p.hAxes, 'CLim', [p.MinColorValue p.MaxColorValue]); % Ensure proper clipping for colorbar
  234. if p.UseFigureColormap
  235. set(p.hFig,'Colormap',p.Colormap);
  236. elseif p.IsGraphics2
  237. % Set the axes colormap and limits
  238. colormap(p.hAxes, p.Colormap);
  239. %set(p.hAxes, 'CLim', [p.MinColorValue p.MaxColorValue]);
  240. end
  241. end
  242. % Generate grid lines
  243. function generateGridLines(p)
  244. if ~strcmp(p.GridLines,'none')
  245. xlim = get(p.hAxes,'XLim');
  246. ylim = get(p.hAxes,'YLim');
  247. for i = 1:diff(xlim)-1
  248. line('Parent',p.hAxes,'XData',[i i]+.5, 'YData', ylim, 'LineStyle', p.GridLines);
  249. end
  250. for i = 1:diff(ylim)-1
  251. line('Parent',p.hAxes,'XData',xlim, 'YData', [i i]+.5, 'LineStyle', p.GridLines);
  252. end
  253. end
  254. end
  255. % Add color bar
  256. function addColorbar(p, mat, textmat)
  257. if isempty(p.Colorbar)
  258. return;
  259. elseif iscell(p.Colorbar)
  260. c = colorbar(p.Colorbar{:});
  261. else
  262. c = colorbar;
  263. end
  264. if p.IsGraphics2
  265. c.Limits = p.hAxes.CLim;
  266. ticks = get(c,'Ticks');
  267. else
  268. if p.ExplicitlyComputeImage || ~p.UseFigureColormap
  269. d = findobj(get(c,'Children'),'Tag','TMW_COLORBAR'); % Image
  270. set(d,'YData', get(p.hAxes,'CLim'));
  271. set(c,'YLim', get(p.hAxes,'CLim'));
  272. end
  273. ticks = get(c,'YTick');
  274. tickAxis = 'Y';
  275. if isempty(ticks)
  276. ticks = get(c,'XTick');
  277. tickAxis = 'X';
  278. end
  279. end
  280. if ~isempty(ticks)
  281. if ischar(textmat) % If format string, format colorbar ticks in the same way
  282. ticklabels = arrayfun(@(x){sprintf(textmat,x)},ticks);
  283. else
  284. ticklabels = num2str(ticks(:));
  285. end
  286. if p.IsGraphics2
  287. set(c, 'TickLabels', ticklabels);
  288. else
  289. set(c, [tickAxis 'TickLabel'], ticklabels);
  290. end
  291. end
  292. end
  293. % ------------------------- Tick Label Functions -------------------------
  294. % Set axes tick labels
  295. function [p, xlab, ylab, hXText, origPos] = setAxesTickLabels(p, xlab, ylab)
  296. if isempty(p.axesInfo) % Not previously a heatmap axes
  297. origPos = [get(p.hAxes,'Position') get(p.hAxes,'OuterPosition')];
  298. else
  299. origPos = p.axesInfo.origAxesPos;
  300. set(p.hAxes, 'Position', origPos(1:4), 'OuterPosition', origPos(5:8));
  301. end
  302. if isempty(p.TickFontSize)
  303. p.TickFontSize = get(p.hAxes, 'FontSize');
  304. else
  305. set(p.hAxes, 'FontSize', p.TickFontSize);
  306. end
  307. if isempty(ylab) % No ticks or labels
  308. set(p.hAxes,'YTick',[],'YTickLabel','');
  309. else
  310. if isnumeric(ylab) % Numeric tick labels
  311. ylab = arrayfun(@(x){num2str(x)},ylab);
  312. end
  313. if ischar(ylab)
  314. ylab = cellstr(ylab);
  315. end
  316. ytick = get(p.hAxes, 'YTick');
  317. ytick(ytick<1|ytick>length(ylab)) = [];
  318. if p.ShowAllTicks || length(ytick) > length(ylab)
  319. ytick = 1:length(ylab);
  320. end
  321. set(p.hAxes,'YTick',ytick,'YTickLabel',ylab(ytick));
  322. end
  323. if p.IsGraphics2
  324. if p.TickTexInterpreter
  325. set(p.hAxes,'TickLabelInterpreter','tex');
  326. else
  327. set(p.hAxes,'TickLabelInterpreter','none');
  328. end
  329. end
  330. % Xlabels are trickier because they could have a TickAngle
  331. hXText = []; % Default value
  332. if isempty(xlab)
  333. set(p.hAxes,'XTick',[],'XTickLabel','');
  334. else
  335. if isnumeric(xlab)
  336. xlab = arrayfun(@(x){num2str(x)},xlab);
  337. end
  338. if ischar(xlab)
  339. xlab = cellstr(xlab);
  340. end
  341. xtick = get(p.hAxes, 'XTick');
  342. xtick(xtick<1|xtick>length(xlab)) = [];
  343. if p.ShowAllTicks || length(xtick) > length(xlab)
  344. xtick = 1:length(xlab);
  345. end
  346. if p.IsGraphics2
  347. set(p.hAxes,'XTick',xtick,'XTickLabel',xlab(xtick),'XTickLabelRotation', p.TickAngle);
  348. else
  349. if p.TickAngle == 0
  350. set(p.hAxes,'XTick',xtick,'XTickLabel',xlab(xtick));
  351. else
  352. hXText = createXTicks(p.hAxes, p.TickAngle, xtick, xlab(xtick), p.TickTexInterpreter);
  353. adjustAxesToAccommodateTickLabels(p.hAxes, hXText);
  354. end
  355. end
  356. end
  357. end
  358. % Create Rotated X Tick Labels (Graphics v1)
  359. function hXText = createXTicks(hAxes, tickAngle, xticks, xticklabels, texInterpreter)
  360. axXLim = get(hAxes, 'XLim');
  361. [xPos, yPos] = calculateTextTickPositions(hAxes, axXLim, xticks);
  362. if texInterpreter
  363. interpreter = 'tex';
  364. else
  365. interpreter = 'none';
  366. end
  367. hXText = text(xPos, yPos, cellstr(xticklabels), 'Units', 'normalized', ...
  368. 'Parent', hAxes, 'FontSize', get(hAxes,'FontSize'), ...
  369. 'HorizontalAlignment', 'right', 'Rotation', tickAngle,...
  370. 'Interpreter', interpreter);
  371. set(hAxes, 'XTick', xticks, 'XTickLabel', '');
  372. end
  373. % Calculate positions of X tick text objects in normalized units
  374. function [xPos, yPos] = calculateTextTickPositions(hAxes, xlim, ticks)
  375. oldunits = get(hAxes,'Units');
  376. set(hAxes,'units','pixels');
  377. axPos = get(hAxes,'position');
  378. set(hAxes,'units',oldunits);
  379. xPos = (ticks - xlim(1))/diff(xlim);
  380. %yPos = -.08 * ones(size(xPos));
  381. yPos = -7.82/axPos(4) * ones(size(xPos));
  382. end
  383. % Adjust axes and tick positions so that everything fits well on screen
  384. function adjustAxesToAccommodateTickLabels(hAxes, hXText)
  385. % The challenge here is that the axes container, especially in a subplot is
  386. % not well defined. The outer position property does not fully span or
  387. % contain the x tick text objects. So here we just shrink the axes height
  388. % just a little so that the axes and tick labels take the same room as the
  389. % axes would have without the ticks.
  390. [axPosP, axPosN, axOPP, axOPN, coPosP, textPosP] = ...
  391. getGraphicsObjectsPositions(hAxes, hXText); %#ok<ASGLU>
  392. header = axOPP(4) + axOPP(2) - axPosP(4) - axPosP(2); % Distance between top of axes and container in pixels;
  393. delta = 5; % To adjust for overlap between area designated for regular ticks and area occupied by rotated ticks
  394. axHeightP = axOPP(4) - header - delta - textPosP(4);
  395. % Fudge axis position if labels are taking up too much room
  396. if textPosP(4)/(textPosP(4)+axHeightP) > .7 % It's taking up more than 70% of total height
  397. axHeightP = (1/.7-1) * textPosP(4); % Minimum axis
  398. end
  399. axHeightN = max(0.0001, axHeightP / coPosP(4));
  400. axPosN = axPosN + [0 axPosN(4)-axHeightN 0 axHeightN-axPosN(4)];
  401. set(hAxes,'Position', axPosN)
  402. end
  403. % Calculate graphics objects positions in pixels and normalized units
  404. function [axPosP, axPosN, axOPP, axOPN, coPosP, textPosP, textPosN] =...
  405. getGraphicsObjectsPositions(hAxes, hXText)
  406. axPosN = get(hAxes, 'Position'); axOPN = get(hAxes, 'OuterPosition');
  407. set(hAxes,'Units','Pixels');
  408. axPosP = get(hAxes, 'Position'); axOPP = get(hAxes, 'OuterPosition');
  409. set(hAxes,'Units','Normalized');
  410. hContainer = get(hAxes,'Parent');
  411. units = get(hContainer,'Units');
  412. set(hContainer,'Units','Pixels');
  413. coPosP = get(hContainer,'Position');
  414. set(hContainer,'Units',units);
  415. set(hXText,'Units','pixels'); % Measure height in pixels
  416. extents = get(hXText,'Extent'); % Get heights for all text objects
  417. extents = vertcat(extents{:}); % Collect heights in one matrix
  418. textPosP = [min(extents(:,1)) min(extents(:,2)) ...
  419. max(extents(:,3)+extents(:,1))-min(extents(:,1)) ...
  420. max(extents(:,4))]; % Find dimensions of text label block
  421. set(hXText,'Units','normalized'); % Restore previous behavior
  422. extents = get(hXText,'Extent'); % Get heights for all text objects
  423. extents = vertcat(extents{:}); % Collect heights in one matrix
  424. textPosN = [min(extents(:,1)) min(extents(:,2)) ...
  425. max(extents(:,3)+extents(:,1))-min(extents(:,1)) ...
  426. max(extents(:,4))]; % Find dimensions of text label block
  427. end
  428. % -------------------------- Callback Functions --------------------------
  429. % Update x-, y- and text-labels with respect to axes limits
  430. function updateLabels(hAxes, axesLimitsChanged)
  431. axInfo = getHeatmapAxesInfo(hAxes);
  432. if isempty(axInfo), return; end
  433. p = axInfo.Parameters;
  434. % Update text font size to fill the square
  435. if ~isempty(p.hText) && ishandle(p.hText(1))
  436. fs = axInfo.FontScaleFactor * getBestFontSize(hAxes);
  437. if fs > 0
  438. set(p.hText,'fontsize',fs,'visible','on');
  439. else
  440. set(p.hText,'visible','off');
  441. end
  442. end
  443. if axesLimitsChanged && ~isempty(axInfo.displayText) % If limits change & text labels are displayed
  444. % Get positions of text objects
  445. textPos = get(p.hText,'Position');
  446. textPos = vertcat(textPos{:});
  447. % Get axes limits
  448. axXLim = get(hAxes, 'XLim');
  449. axYLim = get(hAxes, 'YLim');
  450. % Find text objects within axes limit
  451. ind = textPos(:,1) > axXLim(1) & textPos(:,1) < axXLim(2) & ...
  452. textPos(:,2) > axYLim(1) & textPos(:,2) < axYLim(2);
  453. set(p.hText(ind), 'Visible', 'on');
  454. set(p.hText(~ind), 'Visible', 'off');
  455. end
  456. % Modify Y Tick Labels
  457. if ~isempty(axInfo.ylab)
  458. axYLim = get(hAxes, 'YLim');
  459. if p.ShowAllTicks
  460. yticks = ceil(axYLim(1)):floor(axYLim(2));
  461. else
  462. set(hAxes, 'YTickMode', 'auto');
  463. yticks = get(hAxes, 'YTick');
  464. yticks = yticks( yticks == floor(yticks) );
  465. yticks(yticks<1|yticks>length(axInfo.ylab)) = [];
  466. end
  467. ylabels = repmat({''},1,max(yticks));
  468. ylabels(1:length(axInfo.ylab)) = axInfo.ylab;
  469. set(hAxes, 'YTick', yticks, 'YTickLabel', ylabels(yticks));
  470. end
  471. if ~isempty(axInfo.xlab)
  472. axXLim = get(hAxes, 'XLim');
  473. if p.ShowAllTicks
  474. xticks = ceil(axXLim(1)):floor(axXLim(2));
  475. else
  476. set(hAxes, 'XTickMode', 'auto');
  477. xticks = get(hAxes, 'XTick');
  478. xticks = xticks( xticks == floor(xticks) );
  479. xticks(xticks<1|xticks>length(axInfo.xlab)) = [];
  480. end
  481. xlabels = repmat({''},1,max(xticks));
  482. xlabels(1:length(axInfo.xlab)) = axInfo.xlab;
  483. if ~isempty(axInfo.hXText) % Rotated X tick labels exist
  484. try delete(axInfo.hXText); end %#ok<TRYNC>
  485. axInfo.hXText = createXTicks(hAxes, p.TickAngle, xticks, xlabels(xticks), p.TickTexInterpreter);
  486. set(hAxes, 'UserData', axInfo);
  487. else
  488. set(hAxes, 'XTick', xticks, 'XTickLabel', xlabels(xticks));
  489. end
  490. %adjustAxesToAccommodateTickLabels(hAxes, axInfo.hXText)
  491. end
  492. end
  493. % Callback for data cursor
  494. function output_txt = cursorFun(obj, eventdata)
  495. hAxes = ancestor(eventdata.Target, 'axes');
  496. axInfo = getHeatmapAxesInfo(hAxes);
  497. pos = eventdata.Position;
  498. if ~isempty(axInfo)
  499. try
  500. val = axInfo.displayText{pos(2), pos(1)};
  501. catch %#ok<CTCH>
  502. val = num2str(axInfo.mat(pos(2), pos(1)));
  503. end
  504. if isempty(axInfo.xlab), i = int2str(pos(1)); else i = axInfo.xlab{pos(1)}; end
  505. if isempty(axInfo.ylab), j = int2str(pos(2)); else j = axInfo.ylab{pos(2)}; end
  506. output_txt = sprintf('X: %s\nY: %s\nVal: %s', i, j, val);
  507. else
  508. if length(pos) == 2
  509. output_txt = sprintf('X: %0.4g\nY: %0.4g', pos(1), pos(2));
  510. else
  511. output_txt = sprintf('X: %0.4g\nY: %0.4g\nZ: %0.4g', pos(1), pos(2), pos(3));
  512. end
  513. end
  514. end
  515. % Callback for resize event
  516. function resize(obj, evd)
  517. hAxes = findobj(obj, 'type', 'axes');
  518. for i = 1:length(hAxes)
  519. updateLabels(hAxes(i), false);
  520. end
  521. end
  522. % Extract heatmap parameters for callback
  523. function axInfo = getHeatmapAxesInfo(axH)
  524. axInfo = get(axH, 'UserData');
  525. try
  526. if ~strcmp(axInfo.Type, 'heatmap')
  527. axInfo = [];
  528. end
  529. catch %#ok<CTCH>
  530. axInfo = [];
  531. end
  532. end
  533. % ------------------------- Text Label Functions -------------------------
  534. % Create text labels
  535. function [p, displaytext, factor] = setTextLabels(p, mat, textmat)
  536. if isempty(textmat)
  537. p.hText = [];
  538. displaytext = {};
  539. factor = 0;
  540. return
  541. end
  542. if isscalar(textmat) && textmat % If true convert mat to text
  543. displaytext = arrayfun(@(x){num2str(x)},mat);
  544. elseif ischar(textmat) % If a format string, convert mat to text with specific format
  545. displaytext = arrayfun(@(x){sprintf(textmat,x)},mat);
  546. elseif isnumeric(textmat) && numel(textmat)==numel(mat) % If numeric, convert to text
  547. displaytext = arrayfun(@(x){num2str(x)},textmat);
  548. elseif iscellstr(textmat) && numel(textmat)==numel(mat) % If cell array of strings, it is already formatted
  549. displaytext = textmat;
  550. else
  551. error('texmat is incorrectly specified');
  552. end
  553. if ischar(p.TextColor) && strcmp(p.TextColor,'xor')
  554. colorprop = 'EraseMode';
  555. else
  556. colorprop = 'Color';
  557. end
  558. autoFontSize = getBestFontSize(p.hAxes);
  559. if isempty(p.FontSize)
  560. p.FontSize = autoFontSize;
  561. end
  562. [xpos,ypos] = meshgrid(1:size(mat,2),1:size(mat,1));
  563. if p.FontSize > 0
  564. p.hText = text(xpos(:),ypos(:),displaytext(:),'FontSize',p.FontSize,...
  565. 'HorizontalAlignment','center', colorprop, p.TextColor,'Parent',p.hAxes);
  566. else
  567. p.hText = text(xpos(:),ypos(:),displaytext(:),'Visible','off',...
  568. 'HorizontalAlignment','center', colorprop, p.TextColor,'Parent',p.hAxes);
  569. end
  570. % Calculate factor to scale font size in future callbacks
  571. factor = p.FontSize/autoFontSize;
  572. if isnan(factor), factor = 1; end
  573. if isinf(factor), factor = p.FontSize/6; end
  574. % % Set up listeners to handle appropriate zooming
  575. % addlistener(p.hAxes,{'XLim','YLim'},'PostSet',@(obj,evdata)resizeText);
  576. % try
  577. % addlistener(p.hFig,'SizeChange',@(obj,evdata)resizeText);
  578. % catch
  579. % addlistener(p.hFig,'Resize',@(obj,evdata)resizeText);
  580. % end
  581. % function resizeText
  582. % if ~isempty(hText) && ishandle(hText(1))
  583. % fs = factor*getBestFontSize(hAxes);
  584. % if fs > 0
  585. % set(hText,'fontsize',fs,'visible','on');
  586. % else
  587. % set(hText,'visible','off');
  588. % end
  589. % end
  590. % end
  591. end
  592. % Guess best font size from axes size using heuristics
  593. function fs = getBestFontSize(imAxes)
  594. hFig = ancestor(imAxes,'figure');
  595. magicNumber = 80;
  596. nrows = diff(get(imAxes,'YLim'));
  597. ncols = diff(get(imAxes,'XLim'));
  598. if ncols < magicNumber && nrows < magicNumber
  599. ratio = max(get(hFig,'Position').*[0 0 0 1])/max(nrows,ncols);
  600. elseif ncols < magicNumber
  601. ratio = max(get(hFig,'Position').*[0 0 0 1])/ncols;
  602. elseif nrows < magicNumber
  603. ratio = max(get(hFig,'Position').*[0 0 0 1])/nrows;
  604. else
  605. ratio = 1;
  606. end
  607. fs = min(9,ceil(ratio/4)); % the gold formula
  608. if fs < 4
  609. fs = 0;
  610. end
  611. end
  612. % -------------------------- Colormap Functions --------------------------
  613. % Determine the colormap to use
  614. function p = calculateColormap(p, mat)
  615. if isempty(p.Colormap)
  616. if p.IsGraphics2 && ~p.UseFigureColormap
  617. p.Colormap = colormap(p.hAxes);
  618. else
  619. p.Colormap = get(p.hFig,'Colormap');
  620. end
  621. if isempty(p.ColorLevels)
  622. p.ColorLevels = size(p.Colormap,1);
  623. else
  624. p.Colormap = resamplecmap(p.Colormap, p.ColorLevels);
  625. end
  626. elseif ischar(p.Colormap) || isa(p.Colormap,'function_handle')
  627. if isempty(p.ColorLevels), p.ColorLevels = 64; end
  628. if strcmp(p.Colormap, 'money')
  629. p.Colormap = money(mat, p.ColorLevels);
  630. else
  631. p.Colormap = feval(p.Colormap,p.ColorLevels);
  632. end
  633. elseif iscell(p.Colormap)
  634. p.Colormap = feval(p.Colormap{1}, p.Colormap{2:end});
  635. p.ColorLevels = size(p.Colormap,1);
  636. elseif isnumeric(p.Colormap) && size(p.Colormap,2) == 3
  637. p.ColorLevels = size(p.Colormap,1);
  638. else
  639. error('Incorrect value for colormap parameter');
  640. end % p.Colormap is now a p.ColorLevels-by-3 rgb vector
  641. assert(p.ColorLevels == size(p.Colormap,1));
  642. end
  643. % Resample a colormap by interpolation or decimation
  644. function cmap = resamplecmap(cmap, clevels, xi)
  645. t = cmap;
  646. if nargin < 3
  647. xi = linspace(1,clevels,size(t,1));
  648. end
  649. xi([1 end]) = [1 clevels]; % These need to be exact for the interpolation to
  650. % work and we don't want machine precision messing it up
  651. cmap = [interp1(xi, t(:,1), 1:clevels);...
  652. interp1(xi, t(:,2), 1:clevels);...
  653. interp1(xi, t(:,3), 1:clevels)]';
  654. end
  655. % Generate Red-White-Green color map
  656. function cmap = money(data, clevels)
  657. % Function to make the heatmap have the green, white and red effect
  658. n = min(data(:));
  659. x = max(data(:));
  660. if x == n, x = n+1; end
  661. zeroInd = round(-n/(x-n)*(clevels-1)+1);
  662. if zeroInd <= 1 % Just green
  663. b = interp1([1 clevels], [1 0], 1:clevels);
  664. g = interp1([1 clevels], [1 1], 1:clevels);
  665. r = interp1([1 clevels], [1 0], 1:clevels);
  666. elseif zeroInd >= clevels, % Just red
  667. b = interp1([1 clevels], [0 1], 1:clevels);
  668. g = interp1([1 clevels], [0 1], 1:clevels);
  669. r = interp1([1 clevels], [1 1], 1:clevels);
  670. else
  671. b = interp1([1 zeroInd clevels], [0 1 0], 1:clevels);
  672. g = interp1([1 zeroInd clevels], [0 1 1], 1:clevels);
  673. r = interp1([1 zeroInd clevels], [1 1 0], 1:clevels);
  674. end
  675. cmap = [r' g' b'];
  676. end
  677. % Generate Red-White color map
  678. function cmap = red(levels)
  679. r = ones(levels, 1);
  680. g = linspace(1, 0, levels)';
  681. cmap = [r g g];
  682. end
  683. %#ok<*INUSD>
  684. %#ok<*DEFNU>
  685. %#ok<*INUSL>