How can I build custom xticklabels using numbers and strings? - matlab

I am trying to customize the Xticklabels of my bar graph to have a format of 'number (units)'
So far I have a vector:
scanrate = [2;4;6;8;10];
I want my bar graph to have an x axis of:
2mv/s 4mv/s 6mv/s 8mv/s 10mv/s
If I use xticklabels(num2str(scanrate))
the xticklabels change to the numbers in the scanrate vector. I want to put mv/s after each Xtick.

You can also use strcat :
xticklabels(strcat(num2str(scanrate),' mv/s'))
Please note that it works only when scanrate is a column vector.
Fun fact :
num2str(scanrate) + " mv/s"
also works, but
num2str(scanrate) + ' mv/s'
does not

Build your strings using sprintf(), where the %d flag is for an unsigned integer:
my_labels = {};
for ii = 1:numel(scanrate)
my_labels{ii} = sprintf('%dmv/s', scanrate(ii));
end
figure;
% (...) make your plot
xticklabels(my_labels)
Alternative one-liner, thanks to Wolfie's comment:
my_labels = arrayfun(#(x)sprintf('%dmv/s',x),scanrate,'uni',0);
As a side note: that's normally not what you'd do when creating these type of plots. You'd just have numbers on the axes and a label stating something as "Velocity [mv/s]", rather than having the unit on every single tick label.

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.

Plot means over grouped boxplot in MATLAB?

I am using multiple_boxplot function to generate grouped boxplots:
http://au.mathworks.com/matlabcentral/fileexchange/47233-multiple-boxplot-m
However, instead of medians I want to plot means. First I tried general method:
plot([mean(x)],'dg');
But it did not work. I tried to extract the means and then plot them but that also is not working.
m=[];
for i=1:max(group)
idx=find(group==i);
m=[m nanmean(x(idx))];
end
boxplot(x,group, 'positions', positions);hold on
plot([m],'dg')
What am I doing wrong? And how to plot the means with each boxplot?
Thanks.
You can do the following:
In the function multiple_boxplot change line 48 to:
B = boxplot(x,group, 'positions', positions);
and change the header of the function to:
B = multiple_boxplot(data...
and save the function file.
This won't change anything in how the function works but will let you obtain a handle to the boxplot (B).
Then in your code, create the boxplot as before, but with the output argument B:
B = multiple_boxplot(data...);
And add the following lines:
% compute the mean by group:
M = cellfun(#mean,data);
% convert it to pairs of Y values:
M = mat2cell(repmat(M(:),1,2),ones(size(M,1),1),2);
% change the medians to means:
set(B(6,:),{'YData'},M)

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

Cannot get imagesc to work in Matlab

Would someone kindly please tell me why imagesc is not doing what I want it to:
I am trying to get a grid of chars out like:
http://wetans.net/word-search-worksheets-for-kids
Here is the code:
A = [5,16,18,4,9;
9,10,14,3,18;
2,7,9,11,21;
3,7,2,19,22;
4,9,10,13,8]
AAsChars = char(A + 96);
imagesc(AAsChars);
The short answer is - you are using the wrong function for your job. imagesc will take a map of values and convert it to intensities - one pixel per value. You want it to magically take a character value and turn it into the representation (many pixels) of the character that this represents (without regard to font, etc).
You probably want to create an empty background, then put text characters at the locations you want. Something like (not tested):
figure
imagesc(ones(5,5))
axis off
for t = 1:5
for k = 1:5
text(t, k, sprintf('%c', A(t,k) + 96))
end
end
This should put the string (character) at the location (i,j). Experiment a bit - not sure I have the syntax of text() and the formatting of the string (with "%c") right and can't check right now.
I think you are overcomplicating, this should simply do the trick:
char(A + 96)

Count strings occurrences and plot histogram

Is there any straightforward way to create a histogram from a cell array like the one below? The spacing between the consecutive bars should be exactly the same and the labels of the x-axis should be the corresponding names of the variables below in a vertical orientation.
'w464'
'w462'
'w461'
'w464'
'w461'
'w463'
'w466'
'w461'
I would like to know a better way, as well. Fwiw, I have used countmember in a roundabout way to plot data like this. I.E. if the data you posted was named A
>> B={sort(unique(A)) countmember(sort(unique(A)),A)};
>> bar(B{2});
>> set(gca,'XTickLabel',B{1})
If you have access to the statistics toolbox, grp2idx is very useful:
%# sorting is only necessary if the output should be sorted as well
[idx,label] = grp2idx(sort(A))
hist(idx,unique(idx));
set(gca,'xTickLabel',label)
A solution that only uses built-in functions
[u,~,n] = unique(A(:));
B = accumarray(n, 1, [], #sum);
bar(B)
set(gca,'XTickLabel',u)
You can also use the histogram function as follows:
[C,~,ic] = unique(A);
fig1 = figure;
axes1 = axes('Parent',fig1,'XTickLabel',C,'XTick',1:length(C));
hold(axes1,'on');
histogram(ic)