Plot several lines (looping through line styles in cell array) in Matlab - matlab

I have written this loop to plot each line of results and I get the error message
Error using plot. Invalid first data argument.
So far it looks like this
test=rand(5,6);
xint=[1:1:6];
LineSpec = {'-y', '--m', ':c', '-r.', '-b', ':s'};
for ii=1:5,
plot(xint,test(ii,:),LineSpec(ii),'linewidth',2);
hold on;
legend_str{ii} = num2str(ii);
end
If I use plot(xint,test(ii,:),'-y','linewidth',2) then it works. But how can I avoid the error when looping through line styles?

You should write:
plot(xint,test(ii,:),...
LineSpec{ii},...
'linewidth',2);
LineSpec is a cell array, so LineSpec(ii) returns a cell, while plot asks for a character array as line properties.
you can see the difference when you call LineSpec:
>> LineSpec{1}
ans =
-y
>> LineSpec(1)
ans =
'-y'
When the output is a cell then the answer is indented and has the single-quote marks.

Related

read text file that has repeating blocks of data in matlab

I have a text file of the following form:
...
3
x= 0.10000 3 s
A -1.554489099 -6.345000000 0.55574511
A 6.346000000 -0.134800000 4.45454546
V -1.666613300 -6.333453600 -2.11111145
3
x= 0.20000 3 s
A 7.777755558 -4.033333330 -5.323232323
A 8.122322238 -5.007007007 2.045467754
V -2.443366723 -5.222333337 -6.868889099
3
...
the first line is mostly spaces and a number, the second line has a variable I want to aggregate (x=some number), and ignore the rest of the line, and the next 3 lines have all variables I also want to aggregate in an array form (the A,A,V), and this repeats again and again.
I would like to read this text file such that I will have a vector (1xn) for x values, and a 3D array (3x3xn) for the A,A,V values as function of x.
What I have tried is to read line by line using fgetl(fid) but this gives me for each line a vector of char that I can't work with. I've also tried dlmread but the file structure doesn't work here.
How can I read such a text file in a form that I can use?
So with the help of strsplit this is the solution I've got:
(if there is something better I can do please let me know)
fid = fopen(fn);
n=0;
while true
tline = fgetl(fid);
if ~ischar(tline); break; end %end of file
n=n+1;
c{n}=strsplit(tline);
end
Names={c{3}{1},c{4}{1},c{5}{1}}; % the A A V
k=0;
for n=2:5:numel(c)
k=k+1;
x(k)=str2num(c{n}{3});
for j=1:numel(Names)
AAV(j,:,k)= cell2mat(cellfun(#str2num,c{n+j}(2:4),'un',0));
end
end

Not enough input arguments for CDF

I am trying to plot a CDF for my data, but I get the following error message:
Error using cdf (line 69) Not enough input arguments
My code:
data =cell(1,5);
for j=1:length(container)-7
data{j} = some_values;
cdfplot(data)
So data is a 1x5 cell while inside of it, the values are the following
1x14600double, 1x260double, 1x2222double, 1x3000double, 1x72double
I am expecting a separate line for each of the double arrays i.e. my cdf figure to have 5 lines.
But, the error message confuses me, since I definitely have passed data. Any ideas?
Edited: ok, I have misswritten instead of cdfplot(), I had cdf()... the problem stays the same
The problem was the lack of knowledge on how cells and figures work.
figure;
hold on;
cellfun(#cdfplot,data);
This code did the job :)
In addition to the OP's answer using cellfun, you can also solve this by adjusting how you access the cell array.
Key Idea: Access A with A{} versus A()
% MATLAB R2018b
% Sample data
A = {rand(1,14600) rand(1,260) rand(1,2222) rand(1,3000) rand(1,72)};
Notice that A(1) returns
ans = 1×1 cell array {1×14600 double}
while A{1} returns the full 1x14600 double array (extracts it completely from the cell array).
% Example usage
szA = size(A);
for k = 1:szA(2)
subplot(5,1,k)
cdfplot(A{k})
end
From this example you can see cdfplot works fine.

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

Error using legappend()

I am writing a function that iterates through a loop and adds entries to a plot. When I try to use legappend(), though, I get the error below. I am passing it a string variable.
Error using legend>process_inputs (line 526)
Invalid argument. Type 'help legend' for more information.
Error in legend>make_legend (line 303)
[orient,location,position,children,listen,strings,propargs] =
process_inputs(ha,argin); %#ok
Error in legend (line 257)
[~,msg] = make_legend(ha,args(arg:end),version);
Error in legappend (line 74)
[legend_h,object_h,plot_h,text_strings] = legend(h,allDatah,str);
Here is a minimal example, taken from the MATLAB site
% Some data and old models:
x = (1:10)';
y = [x-5+x.^1.05 x-2 x-3 x-4 x-5];
% Plot the data and old models:
figure
plot(x,y(:,1),'mo','markersize',10);
hold on;
plot(x,y(:,2),'r');
plot(x,y(:,3),'b');
plot(x,y(:,4),'k');
plot(x,y(:,5),'kp');
box off
axis([1 10 -5 20])
legend('data','model 1','model 2','model 3','model 4','location','northwest')
legend boxoff
myNewModel = x - 5.5 + x.^1.1;
plot(x,myNewModel,'m','linewidth',2);
legappend('my new amazing model!')
As mentioned in my comment, MATLAB's graphics engine was significantly overhauled in R2014b. While it brought a plethora of very welcome changes, like any major code overhaul it broke functionality in the existing code base. Relevant here is implementing legends as their own object class rather than as a cobbled together axes object. Given its July 2014 release date, legappend was likely created using R2014a and the logic in the code assumes that the legend is an axes object. This unfortunately breaks in the new graphics system.
Fortunately, the fix isn't as complex as I was anticipating. If you take a look at the properties of the new legend object, there's no documented property for the linked data. Attempting to set the 'String' property manually also has no effect. However, if you look at the final syntax in the function description ([l,icons,plots,txt] = legend(___)), it seems clear that legend has a way to access the appropriate internal properties. And indeed, if you poke around in the legend source, you'll find the 'PlotChildren' property, which is an array of object handles.
Putting it all together we get something like the following:
function legappend_HG2(newStrings)
% Quick & dirty fork of legappend specific to MATLAB versions >= R2014b
% Only supports appending strings to the existing legend handle
% Assumes only one legend is in the current figure
% Add multiple strings by passing it a 1D cell array of strings
% Find our legend object
h = findobj(gcf, 'Type', 'legend');
if ~isempty(h)
% Get existing array of legend strings and append our new strings to it
oldstr = h.String;
if ischar(newStrings)
% Input string is a character array, assume it's a single string and
% dump into a cell
newStrings = {newStrings};
end
newstr = [oldstr newStrings];
% Get line object handles
ploth = flipud(get(gca, 'Children'));
% Update legend with line object handles & new string array
h.PlotChildren = ploth;
h.String = newstr;
end
end
Swapping legappend for legappend_HG2 in the above MWE we get the desired result:
Was getting wrong result in R2016b for exkaza's solution. Hence changed the code in a line a little bit.
function legappend_HG2(newStrings)
% Quick & dirty fork of legappend specific to MATLAB versions >= R2016b
% Only supports appending strings to the existing legend handle
% Assumes only one legend is in the current figure
% Add multiple strings by passing it a 1D cell array of strings
% Find our legend object
h = findobj(gcf, 'Type', 'legend');
if ~isempty(h)
% Get existing array of legend strings and append our new strings to it
oldstr = h.String;
%disp(oldstr);
if ischar(newStrings)
% Input string is a character array, assume it's a single string and
% dump into a cell
newStrings = {newStrings};
end
newstr = [oldstr newStrings];
%disp(newstr);
% Get line object handles
ploth = flipud(get(gca, 'Children'));
% Update legend with line object handles & new string array
%h.PlotChildren = ploth;
h.PlotChildren = [h.PlotChildren; ploth];
h.String = newstr;
end
end

Legend in Matlab Plotting

I have some issues with the legends. I am trying to plot using this code and the code is this:
function PlotNormalPlot(z,i)
hold on
plotTypes = {'b', 'm', 'c'};
TrancheRange = {'100','1000','10000'};
h = normplot(z);
set(h,'color',plotTypes{i})
xlabel('Estimate')
ylabel('Probability')
legendInfo{i} = TrancheRange{i};
legend(legendInfo);
end
It is giving me this error:
Error using legend>process_inputs (line 552)
Cell array argument must be a cell array of strings.
Not sure why this error is there? Need some guidance.
EDIT:
When i tried this:
function PlotNormalPlot(z,i)
hold on
plotTypes = {'b', 'm', 'c'};
TrancheRange = {'100','1000','10000'};
h = normplot(z);
set(h,'color',plotTypes{i})
xlabel('Estimate')
ylabel('Probability')
%legendInfo = TrancheRange{i};
legend(TrancheRange);
end
The legend came out well but the color doesn't get attached to the legend. Not sure why.
Looks like this now:
Try legendInfo=TrancheRange{i}, so legendInfo is a single string.
legendInfo{i} will create a cell array, and, for i=2 as an example, would give you legendInfo={[] '2'} where the first element of legendInfo is an empty array.
I think this could answer your second question. It saves the legend information with the plot handle:
h = normplot(z);
set(h,'color',plotTypes{i},'DisplayName',TrancheRange{i})
legend(h,'show')