How to align xlabels and ylabels in subplot in MATLAB - matlab

I have the following code:
mean_h =[11.9877,13.3937,16.1717];
std_h = [12.5379,10.2732,10.8000];
subplot(2,1,1)
hold on
h = bar(1:3,mean_h,0.2);
errorbar(1:3,mean_h,std_h,'s','MarkerSize',10,...
'MarkerEdgeColor','red','MarkerFaceColor','red');
name = {'4 mics','9 mics','24 mics'};
set(gca,'XTick',[1 2 3],'XTickLabel',{'4 mics','9 mics','24 mics'});
set(gca,'fontsize', 21);
legend({'mean_{hor}', 'std_{hor}'});
grid on
xlabel(' 3 different subsets of horizontal microphone pair combinations of
microphone array 3');
ylabel('Mean/stds rmse`s [°]');
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Mean and standard devaiation for vertical rmse's of microphone array 3
mean_v =[7.3022,11.3737,16.2675];
std_v =[6.2369,9.9813,10.3599];
subplot(2,1,2)
hold on
h = bar(1:3,mean_v,0.2);
errorbar(1:3,mean_v,std_v,'s','MarkerSize',10,...
'MarkerEdgeColor','red','MarkerFaceColor','red');
name = {'4 mics','9 mics','24 mics'};
set(gca,'XTick',[1 2 3],'XTickLabel',{'4 mics','9 mics','24 mics'});
set(gca,'fontsize', 21);
legend({'mean_{ver}', 'std_{ver}'});
grid on
xlabel('3 different subsets of vertical microphone pair combinations of
microphone array 3');
ylabel('Mean/stds rmse`s [°]');
Now when i plot these two subplots i am facing an alignment problem in xlabels and ylabels . They are not aligned. Can anybody help me how can i fix this problem. Thanks

They don't align if the graphs are different. In your case the numbers are different.
You can either do it by hand or use a text(my_x, my_y,'mylabel') and set it up properly. I wrote an example below.
a=1:10;
b=a.^2;
subplot(4,1,1)
plot(a,b)
ylabel(' long text')
subplot(4,1,2)
plot(a,a)
ylabel('long even long text')
subplot(4,1,3)
hold
plot(a,b)
text(0,50,'long text','HorizontalAlignment','center','VerticalAlignment','middle', ...
'FontSize',12,'Rotation',90)
subplot(4,1,4)
hold
plot(a,a)
text(0,5,'long even long text','HorizontalAlignment','center','VerticalAlignment','middle', ...
'FontSize',12,'Rotation',90)
giving the graph below
Notice the last two are aligned, but it was necessary to input the coordinates by hand as i did.
As a tip, you often ca use your data and move it by a fixed percentage (e.g., the minimum -10% graph total length), making it possible to 'automate' your script.
Another way is to use the Position property of the ylabel. But this is similar to the method described above. To do so use
t=ylabel('long text')
t =
Text ( long text) with properties:
String: ' long text'
FontSize: 11
FontWeight: 'normal'
FontName: 'Helvetica'
Color: [0.15 0.15 0.15]
HorizontalAlignment: 'center'
Position: [0.3333 50 -1]
%other things here too
%overwrite the property
% Here you have to put [X_position Y_position Z_position]
t.Position= [0.2 50 -1];
For that to work, every x/ylabel need its own name, or same name, but update the position before you call the command again.

Related

Title over a group of subplots

I want a figure with six plots inside; I split it with subplots. For example
for i = 1:12
subplot(3,4,i)
plot(peaks)
title(['Title plot ',num2str(i)])
end
I would like to add two global titles, let's say a global title for the six plots on the left hand side and another title for the six other plots on the right hand side.
I don't have 2018b version, so I cannot use sgtitle('Subplot Title');. Is it possible use suptitle('my title'); somehow?
I can use text() but resizing the window, the two labels move.
You can use annotation for that, with the location of subplots 1 and 3:
for k = 1:12
sp(k) = subplot(3,4,k);
plot(peaks)
title(['Title plot ',num2str(k)])
end
spPos = cat(1,sp([1 3]).Position);
titleSettings = {'HorizontalAlignment','center','EdgeColor','none','FontSize',18};
annotation('textbox','Position',[spPos(1,1:2) 0.3 0.3],'String','Left title',titleSettings{:})
annotation('textbox','Position',[spPos(2,1:2) 0.3 0.3],'String','Right title',titleSettings{:})
I did not test this, but you can get the handle to a subplot object and then perform the title method on this handle. I would also suggest to then apply the title after the loop.
CODE
for k = 1:12
h(k) = subplot(3, 4, i)
plot(peak)
end
title(h(1), 'Left side')
title(h(8), 'Right side') % find out the right index yourself
Remark:
Do not use i or j as iteration variable for they are already defined in the namespace of MATLAB as imaginary unit.

How to make a legend continue onto more rows when no room?

I have a plot with mutliple lines and I want to display the legend below the box (southoutside). The problem is that currently my legend is too long to fit on a single line. Therefore the question is how do I get a line break in my legend?
Currently I generate the legend as follows:
hLegend = legend([l1,l2,l3,l4], 'This is a very, very long legend text', 'Test2', ...
'A bit longer', 'This is quite long');
set(hLegend,'Fontsize',8,'Location', 'southoutside', 'Orientation','horizontal');
then this occurs:
As you can see I have four lines (there might come more) and the first one has a very long name.
I want to keep the orientation this way to reduce figure space needed and I want to put an automatic line break if the legend exceeds the picture width (i.e. before l3 or l4, here illustrated by the yellow or purple line).
Any ideas on this? I am using a plot width of 15.75 cm.
Edit
Thanks a lot for the answers so far. Although both of the answers provide some opportunities in splitting the legend into two lines, my main problem still occurs. If assuming now that the plot had more then four lines, lets say 20 and I want to have the legend southside horizontal in a way that it uses the least space, is there a way to split the legend not within one legend text, but after one entry. I generated a new figure generally depicting what I am looking for (its made in Paint so it really just shows the general idea).
Edit 2
The columnlegend package available in the Matlab File Exchange unfortunately does not support legends outside of the figure (at least the options are not specified in the description it only names the following possible locations: 'NorthWest', 'NorthEast', 'SouthEast', 'SouthWest'
Help is appreciated.
Intro:
Here's a proof-of-concept of legend text wrapping, using some undocumented outputs of legend and the MATLAB -> python interface. I will first show the code and then provide a brief explanation of why/how it works.
This is done in MATLAB 2016a.
Code:
function q39456339
%% Definitions:
MAX_LENGTH_IN_CHARS = 20;
OPTION = 2;
%% Plot something:
x = 1:10;
figure('Position',[450 400 800 270]);
plot(x,x,x,2*x,x,10-x,x,20-2*x);
%% Using python's TextWrapper to wrap entries:
% web(fullfile(docroot, 'matlab/matlab_external/call-python-from-matlab.html'))
switch OPTION
case 1
[~,hT] = legend({'This is a very, very long legend text', 'Test2', 'A bit longer', ...
'This is quite long'},'Location', 'SouthOutside', 'Orientation','Horizontal',...
'Fontsize',8,'Box','Off');
texts = hT(arrayfun(#(x)isa(x,'matlab.graphics.primitive.Text'),hT));
wrapLegendTexts(texts,MAX_LENGTH_IN_CHARS);
case 2
hL = legend({'This is a very, very long legend text', 'Test2', 'A bit longer', ...
'This is quite long'},'Location', 'SouthOutside', 'Orientation','Horizontal',...
'Fontsize',8,'Interpreter','tex');
TEX_NEWLINE = '\newline';
addNewlinesThroughPython(hL, MAX_LENGTH_IN_CHARS, TEX_NEWLINE);
end
end
%% Helper functions:
function wrapLegendTexts(textObjs,maxlen)
tw = py.textwrap.TextWrapper(pyargs('width', int32(maxlen)));
for ind1 = 1:numel(textObjs)
wrapped = cellfun(#char,cell(wrap(tw,textObjs(ind1).String)), 'UniformOutput', false);
textObjs(ind1).Text.String = reshape(wrapped,[],1);
end
end
function addNewlinesThroughPython(hLeg, maxlen, newlineStr)
tw = py.textwrap.TextWrapper(pyargs('width', int32(maxlen)));
for ind1 = 1:numel(hLeg.PlotChildren)
hLeg.PlotChildren(ind1).DisplayName = char(...
py.str(newlineStr).join(wrap(tw,hLeg.PlotChildren(ind1).DisplayName)));
end
end
Result:
Option 1:
Option 2:
Explanation (Option 1):
First, let's look at the signature of legend:
>> dbtype legend 1
1 function [leg,labelhandles,outH,outM] = legend(varargin)
We can see that the 2nd output returns some sort of handles. When we investigate further:
arrayfun(#class, hT, 'UniformOutput', false)
ans =
'matlab.graphics.primitive.Text'
'matlab.graphics.primitive.Text'
'matlab.graphics.primitive.Text'
'matlab.graphics.primitive.Text'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
And:
hT(1)
ans =
Text (This is a very, very long legend text) with properties:
String: 'This is a very, very long legend text'
FontSize: 9
FontWeight: 'normal'
FontName: 'Helvetica'
Color: [0 0 0]
HorizontalAlignment: 'left'
Position: [0.0761 0.5128 0]
Units: 'data'
Show all properties
Aha! This is the first legend text entry. We see several interesting properties in the above list (more here), but what we care about is String.
Then it's a question of how to wrap said string. Fortunately, this is exactly the example provided in the MATLAB documentation for using the python interface, so I will not go into any details of that. Here's a link to the docs of python's textwrap. The correct version of the page (selectable by a dropdown on the top left) should correspond to your local python version (see output of pyversion).
The rest of my code is just a wrapper around the python interface, to process all legend entries.
Explanation (Option 2):
Here we don't use any extra outputs of legend, and instead modify hLeg.PlotChildren.DisplayName. This property doesn't accept cell arrays of strings (the way for multi-line strings are usually defined in MATLAB), so we need to insert newline "marks" based on syntax the interpreter recognizes (..or character 10 - the ASCII value of a "newline", as shown in excaza's answer). Finding the correct positions for the line break is still done using python, but this time the strings are joined (with the newline mark in between) instead of being converted to a cell column.
Notes:
The 1st option probably provides more control at the expense of some additional required tweaking. One may need to play around with the Texts' Position parameters after wrapping the strings to make the legend look a bit nicer
Assigning the 2nd output of legend changes it behavior slightly (you can see it from the overlapping legend entries in the top figure).
For an automated approach that does not require a local Python installation you can specify a maximum character width and use a regular expression to wrap your text strings accordingly.
For example:
function testcode
x = 1:10;
y1 = x;
y2 = 2*x;
y3 = 3*x;
y4 = 4*x;
l = plot(x, y1, x, y2, x, y3, x, y4);
maxwidth = 20; % Maximum character width of each legend string line
ls = {'This is a very very long legend text', 'Test2', 'A bit longer', 'This is quite long'};
ls = cellfun(#(x)wrapstr(x,maxwidth), ls, 'UniformOutput', false);
legend([l(1),l(2),l(3),l(4)], ls, 'Location', 'SouthOutside', 'Orientation', 'horizontal');
end
function [output] = wrapstr(s, width)
% Split input string s into:
% \S\S{width-1,}: sequences of 1 non-whitespace character followed by
% width-1 or more non-whitespace characters OR
% .{1, width}: sequences of 1 to width of any character.
%
% (?:\\s+|$): Each group is followed by either whitespace or the end of the string
exp = sprintf('(\\S\\S{%u,}|.{1,%u})(?:\\s+|$)', width-1, width);
tmp = regexp(s, exp, 'match');
output = strjoin(deblank(tmp), '\n');
end
Which produces:
The regexp matches Steve Eddin's FEX submission: linewrap

why cant i display both the colorbar's title and ticks simultaneously?

gamma=20;
P=0.1;
N=P.*gamma;
lamdazero=1550;
[lamdapump,lamdasignal] = meshgrid(1540:0.1:1580,1520:0.1:1580);
beta3=0.06;
beta4=-2*10^-4;
c=2*pi*3*10^8;
L=1;
A0=(1./lamdapump) -(1./lamdazero);
B0=(1./lamdapump) -(1./lamdasignal);
Third0=10^-9.*beta3.*(c.^3).*A0.*(B0.^2);
Fourth0=10^-12.*beta4.*(1./2).*c.^4.*(A0.^2).*(B0.^2);
Fourorder=(10^-12).*c.^4.*beta4.*(1/12).*(B0).^4;
deltabeta=Third0+Fourth0+Fourorder;
test2 = deltabeta;
test2(~(deltabeta<=0 & deltabeta>=-4*N)) = nan;
[C,h]=contourf(lamdapump,lamdasignal,test2,[-(4*N):N/2:0],'ShowText','off');
caxis([-8 0]);
xlabel('\lambda_p_u_m_p')
ylabel('\lambda_s_i_g_n_a_l')
title('Contour representing linear phase mismatch in terms of pump and signal wavelength ')
colorbar('YTickLabel',{'-4','-3.5','-3','-2.5','-2','-1.5','-1','-0.5','0'})
h2=colorbar;
HandleOfTitle = get(h2,'Title');
set(HandleOfTitle,'String','\Delta \beta (\gamma P_F_W_M)');
%If i remove the color yticklabel i get my colorbar title and viceversa
%need to know what to do
%The code works just fine
You are creating two colorbars, the second overwrites the first.
colorbar('YTickLabel',{'-4','-3.5','-3','-2.5','-2','-1.5','-1','-0.5','0'})
Creates a colorbar.
h2=colorbar;
Creates another colorbar and removes the first. Get the handle from the first call and remove the second:
h2 = colorbar('YTickLabel',{'-4','-3.5','-3','-2.5','-2','-1.5','-1','-0.5','0'});
Then use the handle to set the title as before.

How to show Miller indexes in MATLAB?

I'm using MATLAB to plot XRD analyses where Miller indexes are used to identify crystallographic plane directions. These indexes contain 3 or 4 numbers and negative value is shown with bar over this number.
In LaTeX it can be written by \([1\bar{1}1]\) or \([1\overline{1}1]\) command.
For labeling spectral lines of XRD standards I'm using this command: Note that negative values are not considered.
std_text_hkl(j)=text(theta{i}(j)-r,0.1,['[' hkl{j} ']'],... % position and label
of j-th line of i-th standard; hkl{j} holds Miller index in string format
'parent',ax_std(i),... % association with axes of i-th standard
'rotation',90,...
'fontsize',12,...
'fontname',Font); % Font holds global font setup
How can I automate creating bar over negative number without using 'Interpreter','latex' property since I would like to be able to change 'FontName'property aswell. At leat I'd like to avoid different fonts in labels and ticks.
EDIT:
Thanks to Magla's comment I got this idea:
Store indexes as 3 column matrix
separate label into 5 text fields
if Miller index is negative draw line over it (top line of text frame)
Actual piece of code:
rr=get(ax_std(i),'xlim'); % read x-axis limits of i-th standard
r=(rr(2)-rr(1))/150; % x-offset of Miller indexes
for j=1:size(dhkl,1)
theta{i}(j)=asin(lambda/(2*dhkl(j,1)))*360/pi(); %calculating of lines
%positions (Bragg's law)
line('parent',ax_std(i),...
'xdata',[theta{i}(j) theta{i}(j)],...
'ydata',[0 dhkl(j,2)],... % j-th line's reflection intensity
'color',[colors(1+mod(i-1,size(colors,1)),1:3)],...
'linewidth',3)
% Miller indexes
if theta{i}(j)>rr(1)&&theta{i}(j)<rr(2) % test if line is inside axes
std_text_lbrace(j)=text(theta{i}(j)-r,0.1,'[',...
'parent',ax_std(i),...
'verticalalignment','bottom',...
'horizontalalignment','left',...
'rotation',90,...
'fontsize',12,...
'fontname',Font);
pos=get(std_text_lbrace(j),'position');
ext=get(std_text_lbrace(j),'extent');
std_text_h(j)=text(pos(1),pos(2)+ext(4)/1.5,int2str(abs(hkl(j,1))),...
'parent',ax_std(i),...
'verticalalignment','bottom',...
'horizontalalignment','left',...
'rotation',90,...
'fontsize',12,...
'fontname',Font); % write 1st Miller index
pos=get(std_text_h(j),'position');
ext=get(std_text_h(j),'extent')
if hkl(j,1)<0 % if negative, draw line over it
wdth=get(ax0,'xlim');
wdth=wdth(2)-wdth(1);
set(std_text_h(j),'color','b','edgecolor','g')
line('parent',ax_std(i),...
'xdata',[pos(1)-wdth/280*ext(3),pos(1)-wdth/280*ext(3)],...
'ydata',[pos(2),pos(2)+ext(4)/wdth*100],...
'color','r')
end
end
I can't fit the line length. For single digit it's too long, for two digits it fits and for more (theoretically) it's way too short. What am I doing wrong? How does MATLAB measure 'extent' property of rotated text?
Here is a piece of code that displays overlines on the top of negative digits. This solution does not use 'interpreter','latex' so that one may choose different fonts. Note that the code uses a set of single textboxes, each one having a \n or char(10) to display on the top line the underscore (char(95), or ' ' for positive digits) and on the bottom line the associated number. One can choose to have two different textboxes to set a particular distance between the underscore and its number. This piece of code does not work for all the fonts though (I would say 90% of my system fonts works fine).
The following code
%Miller indices
miller_ind = [1 -1 -2 3 -3];
%font definition
c = listfonts;
ind_perm = randperm(length(c));
font_names = {'Arial','Times','Courier New',c{ind_perm}};
font_size = 16;
figure('Color','w','Position',[10 10 600 1000]);
py = 0.05;
for ind_font = 1:12
%font name
text(0.03,py,font_names{ind_font},'FontName',font_names{ind_font},'FontSize',font_size);
%plot miller textbox
px = 0.6;
for ii = 1:length(miller_ind)
if miller_ind(ii)<0
text(px,py,[char(95) char(10) num2str(-1*miller_ind(ii)) ],...
'FontName',font_names{ind_font},'FontSize',font_size,'interpreter','none');
else
text(px,py,[' ' char(10) num2str(miller_ind(ii)) ],...
'FontName',font_names{ind_font},'FontSize',font_size,'interpreter','none');
end
px = px + 0.03;
end
py = py + 0.09;
end
gives this result
EDIT
Thank to #Oleg Komarov for his comment. Picture is now directly saved as a .tiff and not via .eps.

Most efficient way of drawing grouped boxplot matlab

I have 3 vectors: Y=rand(1000,1), X=Y-rand(1000,1) and ACTid=randi(6,1000,1).
I'd like to create boxplots by groups of Y and X corresponding to their group value 1:6 (from ACTid).
This is rather ad-hoc and looks nasty
for ii=
dummyY(ii)={Y(ACTid==ii)};
dummyX(ii)={X(ACTid==ii)}
end
Now I have the data in a cell but can't work out how to group it in a boxplot. Any thoughts?
I've found aboxplot function that looks like this but I don't want that, I'd like the builtin boxplot function because i'm converting it to matlab2tikz and this one doesn't do it well.
EDIT
Thanks to Oleg: we now have a grouped boxplot... but the labels are all skew-whiff.
xylabel = repmat({'Bleh','Blah'},1000,1); % need a legend instead, but doesn't appear possible
boxplot([Y(:,end); cfu], {repmat(ACTid,2,1), xylabel(:)} ,'factorgap',10,'color','rk')
set(gca,'xtick',1.5:3.2:50)
set(gca,'xticklabel',{'Direct care','Housekeeping','Mealtimes','Medication','Miscellaneous','Personal care'})
>> ylabel('Raw CFU counts (Y)')
How to add a legend?
I had the same problem with grouping data in a box plot. A further constraint of mine was that different groups have different amounts of data points. Based on a tutorial I found, this seems to be a nice solution I wanted to share with you:
x = [1,2,3,4,5,1,2,3,4,6];
group = [1,1,2,2,2,3,3,3,4,4];
positions = [1 1.25 2 2.25];
boxplot(x,group, 'positions', positions);
set(gca,'xtick',[mean(positions(1:2)) mean(positions(3:4)) ])
set(gca,'xticklabel',{'Direct care','Housekeeping'})
color = ['c', 'y', 'c', 'y'];
h = findobj(gca,'Tag','Box');
for j=1:length(h)
patch(get(h(j),'XData'),get(h(j),'YData'),color(j),'FaceAlpha',.5);
end
c = get(gca, 'Children');
hleg1 = legend(c(1:2), 'Feature1', 'Feature2' );
Here is a link to the tutorial.
A two-line approach (although if you want to retain two-line xlables and center those in the first line, it's gonna be hackish):
Y = rand(1000,1);
X = Y-rand(1000,1);
ACTid = randi(6,1000,1);
xylabel = repmat('xy',1000,1);
boxplot([X; Y], {repmat(ACTid,2,1), xylabel(:)} ,'factorgap',10)
The result:
EDIT
To center labels...
% Retrieve handles to text labels
h = allchild(findall(gca,'type','hggroup'));
% Delete x, y labels
throw = findobj(h,'string','x','-or','string','y');
h = setdiff(h,throw);
delete(throw);
% Center labels
mylbl = {'this','is','a','pain','in...','guess!'};
hlbl = findall(h,'type','text');
pos = cell2mat(get(hlbl,'pos'));
% New centered position for first intra-group label
newPos = num2cell([mean(reshape(pos(:,1),2,[]))' pos(1:2:end,2:end)],2);
set(hlbl(1:2:end),{'pos'},newPos,{'string'},mylbl')
% delete second intra-group label
delete(hlbl(2:2:end))
Exporting as .png will cause problems...