Multiple lines in histogram legend - matlab

I am trying to plot a histogram with a legend that consists of two lines.
Running the following code leads to the error:
Error using matlab.graphics.chart.primitive.Histogram/set
Value cell array handle dimension must match handle vector length.
xErr = randn(1,1000);
[mu, sig] = normfit(xErr);
h = histogram(xErr, 100, 'Normalization','pdf');
% The following command causes the error
set(h_xErr, {'DisplayName'}, {['Standard deviation $\sigma_{x} = $ ', num2str(sigX)]; ['Mean $\mu_x = $ ', num2str(muX)]});
hl = legend('Location', 'NorthWest');
set(hl,'Interpreter','latex');
I also tried the DisplayName property directly with the histogram command but this doesn't work either. According to this question it is necessary that the dimension of the cell array also matches the number of handles which the error states too.
I thought of adding another handle with still the same error.
h = [h; histogram(xErr, 100, 'Normalization','pdf')];
Is there a simple way to get two lines in the legend of a histogramm?
I am using Matlab R2016b

Per the DisplayName documentation, a newline character \n needs to be injected into the text, and this can easily be done through sprintf. One small but important complication is that escaping the standard LaTeX active character \ is required, so sprintf doesn't think LaTeX commands are one of its special characters (some variable names were changed to ensure the code runs):
xErr = randn(1,1000);
[mu, sig] = normfit(xErr);
h = histogram(xErr, 100, 'Normalization','pdf');
set(h,...
'DisplayName',...
sprintf([...
'Standard deviation $\\sigma_{x} = $ ', num2str(sig),...
'\nMean $\\mu_x = $ ', num2str(mu)]));
hl = legend('Location', 'NorthWest');
set(hl,'Interpreter','latex');
I would personally use
xErr = randn(1,1000);
[mu, sig] = normfit(xErr);
histogram(xErr, 100, 'Normalization','pdf');
legText = {...
sprintf([...
'Standard deviation $\\sigma_{x} = %9.7f$ \n ',...
'Mean $\\mu_x = %9.7f$' ],...
[sig,mu])...
};
legend(legText,'Location', 'NorthWest','Interpreter','latex');
but that's just aesthetics.

Related

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

How do I get the Matlab data point labels correct?

I've created some Matlab code which anyone helping can run and see the problem.
When I run the following code, for each data point on my plot I seem to get all 15 labels instead of only 1 specific label.
So how do I get the Matlab data point labels correct for the following code?
Based on the the suggestions, I did the following:
I replaced these two lines of code:
labels = num2str(test_vector_label,'F%d');
labels_cell = cellstr(labels);
With this line of code as suggested:
labels_cell = strread(num2str(test_vector_label),'%s');
Now there are two follow-up questions:
1) A warning appears stating that I should use textscan instead of strread:
labels_cell = textscan(num2str(test_vector_label),'%s');
Then when I use textscan as in the above line of code above, I get an error?
"Error using text
Cell array of strings may only contain string and numeric
matrices"
"Error in Code_Test (line 46)
text(x_val,y_val,labels_cell,'horizontal','left',
'vertical','bottom')"
2) How do I put a letter in front of the number labels? For example, in the original code I had put letter F followed by a number?
%--------------Randomly select training and testing data.-----------
num_data = 35;
data_idx = 1:35;
train_data_idx_tmp = randsample(num_data,20)
train_dataRand_idx = sort(train_data_idx_tmp)
% Lia = ismember(A,B) returns an array the same size as A, containing 1 (true)
% where the elements of A are found in B, and 0 (false) elsewhere.
test_data_idx_tmp = ismember(data_idx,train_dataRand_idx)
test_dataRand_idx = data_idx(~test_data_idx_tmp)'
% Check to see if training and test data index are exclusive.
check_train_test_idx = ismember(train_dataRand_idx,test_dataRand_idx)
%--------------------------------------------------------------------------
% Testing stage.
test_vector = test_dataRand_idx; %Select randomly obtained testing data.
% Training stage.
train_vector = train_dataRand_idx; %Select randomly obtained training
x_val = [1:15];
y_val = 2*[1:15];
plot(x_val,y_val,'or','MarkerFaceColor','r')
grid on
%Put specific data point labels on plots.
test_vector_label = test_vector';
labels = num2str(test_vector_label,'F%d');
labels_cell = cellstr(labels);
text(x_val,y_val,labels_cell,'horizontal','left', 'vertical','bottom')
Your variable labels_cell is a 1x1 string cell not an array of strings. Replace
labels = num2str(test_vector_label,'F%d');
labels_cell = cellstr(labels);
with
labels_cell = strread(num2str(test_vector_label),'%s');

Reading parameters from a text file into the workspace

I have a file which has the following information:
% ---------------------- location details --------------------------
%
% lat : latitude [minimum = -90, maximum = 90, unit =
% degrees north]
% lon : longitude [ minimum = -360, maximum = 360, unit =
% deg east]
% z: altitude (above sea level, m)
%---------------------------------------------------------------
% location:
lat = 54.35
lon = -2.9833
This is a small section of the file.
I would like to read some of this information into MATLAB, where the information can then be used to perform some calculations. The part of the file that I would like to read into MATLAB are those in the text file that are not commented, i.e have a % at the start of the line, and the variable should then be saved in the workspace. For example, I would like to have:
lat = 54.35
lon = -2.9833
in the workspace.
How would I go about this? I have read about textscan and fopen, although these don't really seem to help me in this instance.
The quick-and-dirty approach
The simplest solution I could think of to read this file indeed employs textscan :) and since the lines are conveniently written in valid MATLAB syntax, you could use eval later to evaluate them. Start by reading each line as one string (ignoring the comments in the header)
fid = fopen(filename);
C = textscan(fid, '%s', 'Delimiter', '', 'CommentStyle', '%')
fclose(fid);
Then feed the lines one by one into eval to create the variables in the MATLAB workspace:
cellfun(#eval, C{1});
What this does is interpret the line as a MATLAB command, i.e create variables as named in the file and assign the appropriate values. If you want to suppress the output of eval, you can use evalc instead to "absorb the output":
cellfun(#evalc, C{1}, 'UniformOutput', false);
This should work for your basic example, but it would fail if you have more than one instance of any parameter. Also note that the eval family is notoriously slow.
A more robust approach
If the lines in your file structure have the parameter name = number pattern, you can read the lines more intelligently:
fid = fopen(filename);
C = textscan(fid, '%[^= ]%*[= ]%f', 'CommentStyle', '%')
fclose(fid);
The %[^= ] in the pattern matches the first characters until the first space or equality sign. The %*[ =] ignores the equality sign and any trailing spaces, and then the numerical value is matched with %f. The resulting cell array C stores the parameter names in the first cell and their corresponding values in the second cell.
Now it's up to you to manipulate the parsed data. For instance, to extract all values of lat and lon, you can do this:
lat = C{2}(strcmp(C{1}, 'lat'));
lon = C{2}(strcmp(C{1}, 'lon'));
If you have more than one "lat" line, lat will be an array holding all these values.
Here's another quick and dirty way:
fp = fopen('foo.txt');
found = 1;
while ~feof(fp)
line = fgetl(fp);
if (line(1) ~= '%') && ischar(line)
value(found) = sscanf(line,'%*s %*s %f');
found = found + 1;
end
end
The %*s skips the 'lat' or 'long' and the '='.
The example you provided is kinda well-behaved, therefore the following solution might need some tailoring. However, I would recommend it against any eval():
% Read whole file ignoring lines that start with '%' and using '=' as delimiter
fid = fopen('test.txt');
s = textscan(fid,'%s%f', 'CommentStyle','%','Delimiter','=');
fclose(fid);
% Identify lines with latitude and those with longitude
idxLat = strncmpi('lat',s{1},3);
idxLon = strncmpi('lon',s{1},3);
% Store all latitudes and longitudes
lat = s{2}(idxLat);
lon = s{2}(idxLon);
Gets you a structure with field names matching parameter names, accepts comma-separated lists. List any parameters that should stay as strings in char_params
char_params={};
fid = fopen(filename);
% Load lines into cell (1x1) containing cell array s (Nx1),
% skipping lines starting with % and cutting off anything after % in a line
s = textscan(fid,'%s', 'CommentStyle','%','Delimiter','%');
fclose(fid);
% access the lines strings s{1}, split across '=' and remove whitespace on both sides
s=strtrim(split(s{1},'='));
% Interpret parameters and save to structure
for ind=1:length(s)
% User says which parameters are strings
if any(strcmpi(s{ind,1},char_params))
file_struct.(s{ind,1})=s{ind,2};
% Otherwise, assume they are numbers or numeric row arrays
else
% remove parentheses and brackets
trim_s=regexprep(s(ind,2),'[[]()]','');
% convert comma-separated lists into row arrays
file_struct.(s{ind,1})=str2double(split(trim_s{1},',')).';
end
end

Using a colon for indexing in matrices of unknown dimensions

When indexing matrices in MATLAB, can I specify only the first or last n dimensions, and have all others dimensions "selected automatically"?
For example, I am writing a function which takes in an image, and displays it with imshow, which can either display a 3-D color image (e.g 1024×768×3) or a 2-D monochrome array (e.g 1024x768).
My function does not care about how many color channels the image has, imshow will take care of that. All I want to do is pass parameters to select a single region:
imshow(frame(x1:x2, y1:y2, :))
What do I put in place of the last colon to say "include all the others dimensions"?
You can use comma-separated-list expansion together with the ':' indexing.
Suppose your input is:
A = rand([7,4,2,3]);
To retrieve only first 2:
cln = {':', ':'};
A(cln{:})
To retrieve the last 3:
cln = {1, ':', ':', ':'};
A(cln{:})
Which can be generalized with:
sten = 2:3; % Which dims to retrieve
cln(1:ndims(A)) = {1};
cln(sten) = {':'};
A(cln{:})
Following from Oleg's answer, here is a function that will work if you are selecting from several of the first dimensions. If other dimensions are needed, I think you can see how to modify.
function [dat] = getblock2(dat, varargin)
%[dat] = getblock(dat, varargin) select subarray and retain all others
% unchanged
%dat2 = getblock(dat, [1,2], [3,5]) is equivalent to
% dat2 = dat(1:2, 3:5, :, :, :) etc.
%Peter Burns 4 June 2013
arg1(1:ndims(dat)) = {':,'};
v = cell2mat(varargin);
nv = length(v)/2;
v = reshape(v,2,nv)';
for ii=1:nv
arg1{ii} = [num2str(v(ii,1)),':',num2str(v(ii,2)),','];
end
arg2 = cell2mat(arg1);
arg2 = ['dat(',arg2(1:end-1),')'];
dat = eval(arg2);

MATLAB - Labeling Curves During Iteration

I want to show the p value that was used to generate each curve next to each of the curves plotted. Note that since there is a plot of E and -E, the same p value should be next to both. I've been attempting this for a while and I have not come across anything super useful.
t = -3.1;%coupling
a = 1;%distance between r1 and r3
n = 5;%latice vector span in a1 direction
m = 1;%latice vector span in a2 direction
i = -7;%unique axial vector t_hat direction
j = 11;%unique axial vector c_hat direction
max_p = abs((n*(i+j/2)-j*(m+n/2)));%# of unique p values
La = sqrt(3)*sqrt(m^2+n*m+n^2)*a/gcd(2*n+m,2*m+n);%unit cell length
C = sqrt(n^2+n*m+m^2);%circumference of the nanotube
hold on;
for p=0:1:max_p
kt = -pi/La:.05:pi/La;
kc = 2*pi*p/C;
ka1 = kc*a*.5*(2*n+m)/C + kt*a*sqrt(3)*.5*m/C;
ka2 = kc*a*.5*(n+2*m)/C - kt*a*sqrt(3)*.5*n/C;
E = abs(t+t*exp(1i*ka2)+t*exp(1i*ka1));
title_ = sprintf('(%d,%d) Carbon Nanotube Dispersion Diagram',n,m);
title(title_);
xlabel('k_{t}a');
ylabel('Energy (eV)');
plot(kt,E);
plot(kt,-E);
end
There is a command named text that writes comments into the figures,
http://www.mathworks.se/help/techdoc/ref/text.html
with if you can't solve it with that and the to string operation i misunderstood the question
First, do you need to plot both E and -E? Since these are the same except for their sign you don't really add any information to the plot by having -E there as well. However, if you do need both lines, then just construct an array of strings for the legend, during the loop, which has each string included twice (once for E and once for -E).
... Initial calculations ...
hold on;
for p=0:1:max_p
kt = -pi/La:.05:pi/La;
kc = 2*pi*p/C;
ka1 = kc*a*.5*(2*n+m)/C + kt*a*sqrt(3)*.5*m/C;
ka2 = kc*a*.5*(n+2*m)/C - kt*a*sqrt(3)*.5*n/C;
E = abs(t+t*exp(1i*ka2)+t*exp(1i*ka1));
plot(kt,E);
plot(kt,-E);
% Construct array containing legend text
legend_text{2*(p+1)-1} = strcat('p=', num2str(p));
legend_text{2*(p+1)} = strcat('p=', num2str(p));
end
title_ = sprintf('(%d,%d) Carbon Nanotube Dispersion Diagram',n,m);
title(title_);
xlabel('k_{t}a');
ylabel('Energy (eV)');
legend(legend_text)
I am sure there is a more elegant way of constructing the legend text, but the above code works. Also, notice that I moved the calls to xlabel, ylabel and title to outside of the loop. This way they are only called once and not for each iteration of the loop.
Finally, you need to take care to ensure that each iteration of the loop plots with a different line colour or line style (see edit below). You could colour/style each pair of E and -E lines the same for a given iteration of the loop and just display the legend for E (or -E), which would obviously halve the number of legend entries. To do this you will need to hide one of line's handle visibility - this prevents it from getting an item in the legend. To do this use the following in your loop:
plot(kt, E);
plot(kt,-E, 'HandleVisibility', 'off');
% Construct array containing legend text
legend_text{p+1} = strcat('p=', num2str(p));
Finally, it is best to include clear all at the top of your Matlab scripts.
Edit: To have each plotted line use a different colour for each iteration of your loop use something like the following
... initial calculations ...
cmap = hsv(max_p); % Create a max_p-by-3 set of colors from the HSV colormap
hold on;
for p = 0:1:max_p
plot(kt, E, 'Color', cmap(p,:)); % Plot each pair of lines with a different color
plot(kt, -E, 'Color', cmap(p,:));
end