brewermap_view.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. function [map,num,typ,scheme] = brewermap_view(N,scheme) %#ok<*ISMAT>
  2. % An interactive figure for ColorBrewer colormap selection. With demo!
  3. %
  4. % (c) 2014-2020 Stephen Cobeldick
  5. %
  6. % View Cynthia Brewer's ColorBrewer colorschemes in a figure.
  7. %
  8. % * Two colorbars give the colorscheme in color and grayscale.
  9. % * A button toggles between 3D-cube and 2D-lineplot of the RGB values.
  10. % * A button toggles an endless demo cycle through the colorschemes.
  11. % * A button reverses the colormap.
  12. % * 35 buttons select any ColorBrewer colorscheme.
  13. % * Text with the colorscheme's type (Diverging/Qualitative/Sequential)
  14. % * Text with the colorscheme's number of nodes (defining colors).
  15. %
  16. %%% Syntax:
  17. % brewermap_view
  18. % brewermap_view(N)
  19. % brewermap_view(N,scheme)
  20. % brewermap_view([],...)
  21. % brewermap_view(axes/figure handles,...) % see "Adjust Colormaps"
  22. % [map,scheme] = brewermap_view(...)
  23. %
  24. % Calling the function with an output argument blocks MATLAB execution until
  25. % the figure is deleted: the final colormap and colorscheme are then returned.
  26. %
  27. %% Adjust Colormaps of Figures or Axes %%
  28. %
  29. % Only R2014b or later. Provide axes or figure handles as the first input
  30. % and their colormaps will be updated in real-time by BREWERMAP_VIEW.
  31. %
  32. %%% Example:
  33. %
  34. % >> S = load('spine');
  35. % >> image(S.X)
  36. % >> brewermap_view(gca)
  37. %
  38. %% Input and Output Arguments %%
  39. %
  40. %%% Inputs (*=default):
  41. % N = NumericScalar, an integer to define the colormap length.
  42. % = *[], colormap length of one hundred and twenty-eight (128).
  43. % = NaN, same length as the defining RGB nodes (useful for Line ColorOrder).
  44. % = Array of axes/figure handles. R2014b or later only.
  45. % scheme = CharRowVector, a ColorBrewer colorscheme name.
  46. %
  47. %%% Outputs (these block code execution until the figure is closed!):
  48. % map = NumericMatrix, the colormap defined when the figure is closed.
  49. % num = NumericVector, the number of nodes defining the ColorBrewer colorscheme.
  50. % typ = CharRowVector, the colorscheme type: 'Diverging'/'Qualitative'/'Sequential'.
  51. %
  52. % See also BREWERMAP CUBEHELIX RGBPLOT COLORMAP COLORMAPEDITOR COLORBAR UICONTROL ADDLISTENER
  53. %% Input Wrangling %%
  54. %
  55. persistent ax2D ln2D ax3D pt3D txtH is2D cbAx cbIm pTxt pSld bEig bGrp bRev scm isr
  56. %
  57. new = isempty(ax2D)||~ishghandle(ax2D);
  58. dfn = 128;
  59. upd = false;
  60. upb = false;
  61. hgv = [];
  62. nmr = dfn;
  63. %
  64. err = 'First input must be a real positive scalar numeric or [] or NaN.';
  65. if nargin==0 || isnumeric(N)&&isequal(N,[])
  66. N = dfn;
  67. elseif isnumeric(N)
  68. assert(isscalar(N),'SC:brewermap_view:NotScalarNumeric',err)
  69. assert(isnan(N)||isreal(N)&&isfinite(N)&&fix(N)==N&&N>0,...
  70. 'SC:brewermap_view:NotRealPositiveNotNaN',err)
  71. N = double(N);
  72. elseif all(ishghandle(N(:))) % R2014b or later
  73. assert(isgraphics(N(:),'axes')|isgraphics(N(:),'figure'),...
  74. 'SC:brewermap_view:NotAxesNorFigureHandles',...
  75. 'First input may be an array of figure or axes handles.')
  76. hgv = N(:);
  77. nmr = arrayfun(@(h)size(colormap(h),1),hgv);
  78. N = nmr(1);
  79. else
  80. error('SC:brewermap_view:UnsupportedInput',err)
  81. end
  82. %
  83. [mcs,mun,pyt] = brewermap('list');
  84. %
  85. % Check BREWERMAP output:
  86. tmp = find([any(diff(double(char(pyt)),1),2);1]);
  87. assert(isequal(tmp,[9;17;35]),'SC:brewermap_view:SchemeSequence',...
  88. 'The BREWERMAP function returned an unexpected scheme sequence.')
  89. %
  90. % Default pseudo-random colorscheme:
  91. if nargin==0 || new
  92. isr = false;
  93. scm = mcs{1+mod(round(now*1e7),numel(mcs))};
  94. end
  95. % Parse input colorscheme:
  96. if nargin==2
  97. assert(ischar(scheme)&&ndims(scheme)==2&&size(scheme,1)==1,...
  98. 'SC:brewermap_view:NotCharacterVector',...
  99. 'Second input <scheme> must be a 1xN char vector.')
  100. % Check if a reversed colormap was requested:
  101. isr = strncmp(scheme,'*',1);
  102. scm = scheme(1+isr:end);
  103. end
  104. %
  105. if isnan(N)
  106. N = mun(strcmpi(scm,mcs));
  107. end
  108. %
  109. %% Ensure Figure Exists %%
  110. %
  111. % LHS and RHS slider bounds/limits, and slider step sizes:
  112. lbd = 1;
  113. rbd = dfn;
  114. stp = [1,10]; % [minor,major]
  115. %
  116. % Define the 3D cube axis order:
  117. xyz = 'RGB'; % choose order
  118. [~,xyz] = ismember(xyz,'RGB');
  119. %
  120. if new % Create a new figure.
  121. %
  122. % Figure parameters:
  123. M = 9; % buttons per column
  124. gap = 0.01; % gaps
  125. bth = 0.04; % demo button height
  126. btw = 0.10; % demo button width
  127. bgh = 0.40; % button group height
  128. cbw = 0.23; % colorbar width (both together)
  129. cbh = 1-3*gap-bth; % colorbar height
  130. axh = 1-bgh-2*gap; % axes height
  131. axw = 1-cbw-2*gap; % axes width
  132. bgw = axw-btw-gap; % button group width
  133. %
  134. figH = figure('HandleVisibility','callback', 'Color','white',...
  135. 'IntegerHandle','off', 'NumberTitle','off', 'Units','normalized',...
  136. 'Name','ColorBrewer Interactive ColorScheme Selector',...
  137. 'MenuBar','figure', 'Toolbar','none', 'Tag',mfilename);
  138. %
  139. % Add 2D lineplot:
  140. ax2D = axes('Parent',figH, 'Position',[gap,bgh+gap,axw,axh], 'Box','on',...
  141. 'ColorOrder',[1,0,0; 0,1,0; 0,0,1; 0.6,0.6,0.6], 'HitTest','off',...
  142. 'Visible','off', 'XLim',[0,1], 'YLim',[0,1], 'XTick',[], 'YTick',[]);
  143. ln2D = line([0,0,0,0;1,1,1,1],[0,0,0,0;1,1,1,1], 'Parent',ax2D,...
  144. 'Visible','off', 'Linestyle','-', 'Marker','.');
  145. %
  146. % Add 3D scatterplot:
  147. ax3D = axes('Parent',figH, 'OuterPosition',[0,bgh,axw+2*gap,1-bgh],...
  148. 'Visible','on', 'XLim',[0,1], 'YLim',[0,1], 'ZLim',[0,1], 'HitTest','on');
  149. pt3D = patch('Parent',ax3D, 'XData',[0;1], 'YData',[0;1], 'ZData',[0;1],...
  150. 'Visible','on', 'LineStyle','none', 'FaceColor','none', 'MarkerEdgeColor','none',...
  151. 'Marker','o', 'MarkerFaceColor','flat', 'MarkerSize',10, 'FaceVertexCData',[1,1,0;1,0,1]);
  152. view(ax3D,3);
  153. grid(ax3D,'on')
  154. lbl = {'Red','Green','Blue'};
  155. xlabel(ax3D,lbl{xyz(1)})
  156. ylabel(ax3D,lbl{xyz(2)})
  157. zlabel(ax3D,lbl{xyz(3)})
  158. %
  159. % Add warning text:
  160. txtH = text('Parent',ax2D, 'Units','normalized', 'Position',[1,1],...
  161. 'HorizontalAlignment','right', 'VerticalAlignment','top', 'Color','k');
  162. %
  163. % Add demo button:
  164. demo = uicontrol(figH, 'Style','togglebutton', 'Units','normalized',...
  165. 'Position',[1-cbw/2,1-bth-gap,cbw/2-gap,bth], 'String','Demo',...
  166. 'Max',1, 'Min',0, 'Callback',@bmvDemo); %#ok<NASGU>
  167. % Add 2D/3D button:
  168. is2D = uicontrol(figH, 'Style','togglebutton', 'Units','normalized',...
  169. 'Position',[1-cbw/1,1-bth-gap,cbw/2-gap,bth], 'String','2D / 3D',...
  170. 'Max',1, 'Min',0, 'Callback',@bmv2D3D);
  171. % Add reverse button:
  172. bRev = uicontrol(figH, 'Style','togglebutton', 'Units','normalized',...
  173. 'Position',[bgw+2*gap,bgh-bth,btw,bth], 'String','Reverse',...
  174. 'Max',1, 'Min',0, 'Callback',@bmvRevM);
  175. %
  176. % Add colorbars:
  177. C(1,1,:) = [1,1,1];
  178. cbAx(2) = axes('Parent',figH, 'Visible','off', 'Units','normalized',...
  179. 'Position',[1-cbw/2,gap,cbw/2-gap,cbh], 'YLim',[0.5,1.5],...
  180. 'YDir','reverse', 'HitTest','off');
  181. cbAx(1) = axes('Parent',figH, 'Visible','off', 'Units','normalized',...
  182. 'Position',[1-cbw/1,gap,cbw/2-gap,cbh], 'YLim',[0.5,1.5],...
  183. 'YDir','reverse', 'HitTest','off');
  184. cbIm(2) = image('Parent',cbAx(2), 'CData',C);
  185. cbIm(1) = image('Parent',cbAx(1), 'CData',C);
  186. %
  187. % Add parameter slider, listener, and corresponding text:
  188. sv = mean([lbd,rbd],2);
  189. pTxt = uicontrol(figH,'Style','text', 'Units','normalized',...
  190. 'Position',[bgw+2*gap,bgh-2*bth-gap,btw,bth], 'String','X');
  191. pSld = uicontrol(figH,'Style','slider', 'Units','normalized',...
  192. 'Position',[bgw+2*gap,gap,btw,bgh-2*bth-gap], 'Min',lbd(1), 'Max',rbd(1),...
  193. 'SliderStep',stp(1,:)/(rbd(1)-lbd(1)), 'Value',sv(1));
  194. addlistener(pSld, 'Value', 'PostSet',@bmvSldr);
  195. %
  196. % Add colorscheme button group:
  197. bGrp = uibuttongroup('Parent',figH, 'BorderType','none', 'Units','normalized',...
  198. 'BackgroundColor','white', 'Position',[gap,gap,bgw,bgh-gap]);
  199. % Determine button locations:
  200. Z = 1:numel(mcs);
  201. Z = Z+(Z>17);
  202. C = (ceil(Z/M)-1)/4;
  203. R = (M-1-mod(Z-1,M))/M;
  204. % Add colorscheme buttons to group:
  205. for jj = numel(mcs):-1:1
  206. bEig(jj) = uicontrol('Parent',bGrp, 'Style','Toggle', 'String',mcs{jj},...
  207. 'Unit','normalized', 'Position',[C(jj),R(jj),1/4,1/M]);
  208. end
  209. set(bGrp,'SelectionChangeFcn',@bmvChgS);
  210. %
  211. end
  212. %
  213. %% Nested Functions %%
  214. %
  215. function str = makeName()
  216. str = '*';
  217. str = [str(isr),scm];
  218. end
  219. %
  220. function bmvUpDt()
  221. % Update all graphics objects in the figure.
  222. %
  223. % Get ColorBrewer colormap and grayscale equivalent:
  224. [map,num,typ] = brewermap(N,makeName());
  225. mag = map*[0.298936;0.587043;0.114021];
  226. %
  227. % Update colorbar values:
  228. set(cbAx, 'YLim', [0,abs(N)+(N==0)]+0.5);
  229. set(cbIm(1), 'CData',reshape(map,[],1,3))
  230. set(cbIm(2), 'CData',repmat(mag,[1,1,3]))
  231. %
  232. % Update 2D line / 3D patch values:
  233. if get(is2D, 'Value') % 2D
  234. set(ln2D, 'XData',linspace(0,1,abs(N)));
  235. set(ln2D,{'YData'},num2cell([map,mag],1).');
  236. else % 3D
  237. set(pt3D,...
  238. 'XData',map(:,xyz(1)),...
  239. 'YData',map(:,xyz(2)),...
  240. 'ZData',map(:,xyz(3)),...
  241. 'FaceVertexCData',map)
  242. end
  243. %
  244. % Update reverse button:
  245. set(bRev, 'Value',isr)
  246. %
  247. % Update warning text:
  248. str = {[typ,' '];sprintf('%d Nodes ',num)};
  249. set(txtH,'String',str);
  250. %
  251. % Update parameter value text:
  252. set(pTxt(1), 'String',sprintf('N = %.0f',N));
  253. %
  254. % Update external axes/figure:
  255. nmr(1) = N;
  256. for k = 1:numel(hgv)
  257. colormap(hgv(k),brewermap(nmr(k),makeName()));
  258. end
  259. %
  260. drawnow()
  261. end
  262. %
  263. function bmv2D3D(h,~)
  264. % Switch between 2D-line and 3D-cube representation.
  265. %
  266. if get(h,'Value') % 2D
  267. set(ax3D, 'HitTest','off', 'Visible','off')
  268. set(ax2D, 'HitTest','on', 'Visible','on')
  269. set(pt3D, 'Visible','off')
  270. set(ln2D, 'Visible','on')
  271. else % 3D
  272. set(ax2D, 'HitTest','off', 'Visible','off')
  273. set(ax3D, 'HitTest','on', 'Visible','on')
  274. set(ln2D, 'Visible','off')
  275. set(pt3D, 'Visible','on')
  276. end
  277. %
  278. bmvUpDt();
  279. end
  280. %
  281. function bmvChgS(~,e)
  282. % Change the colorscheme.
  283. %
  284. scm = get(e.NewValue,'String');
  285. %
  286. bmvUpDt()
  287. end
  288. %
  289. function bmvRevM(h,~)
  290. % Reverse the colormap.
  291. %
  292. isr = logical(get(h,'Value'));
  293. %
  294. bmvUpDt()
  295. end
  296. %
  297. function bmvSldr(~,~)
  298. % Update the slider position.
  299. %
  300. if ~upd
  301. return
  302. end
  303. %
  304. N = round(get(pSld,'Value'));
  305. %
  306. bmvUpDt()
  307. end
  308. %
  309. function bmvDemo(h,~)
  310. % Display all ColorBrewer colorschemes sequentially.
  311. %
  312. cnt = 0;
  313. while ishghandle(h)&&get(h,'Value')
  314. cnt = mod(cnt+1,pow2(53));
  315. %
  316. if mod(cnt,23)<1
  317. ids = 1+mod(find(strcmpi(scm,mcs)),numel(mcs));
  318. scm = mcs{ids};
  319. try %#ok<TRYNC>
  320. set(bGrp, 'SelectedObject',bEig(ids));
  321. end
  322. end
  323. %
  324. if mod(cnt,69)<1
  325. isr = ~isr;
  326. end
  327. %
  328. upb = (upb || N<=1) && N<dfn;
  329. N = N - 1 + 2*upb;
  330. %
  331. % Update slider position:
  332. upd = false;
  333. try %#ok<TRYNC>
  334. set(pSld, 'Value',N)
  335. bmvUpDt()
  336. end
  337. upd = true;
  338. %
  339. % Faster/slower:
  340. pause(0.1);
  341. end
  342. %
  343. end
  344. %
  345. %% Initialize the Figure %%
  346. %
  347. set(bGrp,'SelectedObject',bEig(strcmpi(scm,mcs)));
  348. set(pSld,'Value',max(lbd,min(rbd,N)));
  349. upd = true;
  350. bmvUpDt()
  351. %
  352. if nargout
  353. waitfor(ax2D);
  354. scheme = makeName();
  355. else
  356. clear map
  357. end
  358. %
  359. end
  360. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%brewermap_view
  361. %
  362. % Copyright (c) 2014-2020 Stephen Cobeldick
  363. %
  364. % Licensed under the Apache License, Version 2.0 (the "License");
  365. % you may not use this file except in compliance with the License.
  366. % You may obtain a copy of the License at
  367. %
  368. % http://www.apache.org/licenses/LICENSE-2.0
  369. %
  370. % Unless required by applicable law or agreed to in writing, software
  371. % distributed under the License is distributed on an "AS IS" BASIS,
  372. % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  373. % See the License for the specific language governing permissions and limitations under the License.
  374. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%license