Removing Particular Objects From a Legend - matlab

I need your guys help in solving a small problem Im facing. When I want to depict f1 and f2 using bar function, I need to exclude f2 annotation objects in the legend tab of the figure, but the set syntax written below seems to give the error mentioned.
The code is as below:
f1= bar([SN, SN, SN], [Class_Work, Final_Exam, Shift_Grade'-Grade], K, 'stacked');
f2= bar([SN(idx), SN(idx), SN(idx)], [Class_Work(idx), Final_Exam(idx), SG(idx)-Grade(idx)], K*dy/dx, 'stacked', 'LineWidth', 2.5);
set(f1,{'DisplayName'},{'Mid-Term','Final-Exam','Shift'}')
legend('location','NorthEast','Orientation','horizontal');
% in order to Exclude f2 indices from legend: (BUT SEEMS NOT WORKING based on error!)
set(get(get(f2,'Annotation'),'LegendInformation'),...
'IconDisplayStyle','off');
After running it gives this error in command-window including the correct figure, but with all annotation objects:
??? Error using ==> get
Conversion to double from cell is not possible.
Error in ==> set(get(get(f2,'Annotation'),'LegendInformation'),...
The Figure, which I need is that: data4, data5, and data6 graphic objects (related to f2) in the legend tab NOT to appear, when drawing f2.
I appreciate your helps in advance.

There is a special syntax to call legend which should help in your case. From Matlab documentation:
legend(h, 'string1', 'string2', ...);
displays a legend on the plot containing the objects identified by the handles in the vector h and uses the specified strings to label the corresponding graphics object (line, barseries, etc.).
So, in your case you just should do:
legend(f1, 'Mid-Term', 'Final-Exam', 'Shift');
and then modify other properties of a legend (location, orientation, etc.) accordingly.
UPDATE:
Alternatively, to make your initial code work, you should do:
annots = get(h,'Annotation');
for i=1:length(annots)
set(get(annots{i},'LegendInformation'),'IconDisplayStyle','off');
end
annots = get(h,'Annotation') returns cell array and then you just operate on each cell (i.e. annots{i}) of this array.

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

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

How to access Matlab's handle object's sub-property: 'allowedStyles'

I would like to progmatically access the 'sub-property' of a lineseries object's 'MarkerFaceColor' property called 'allowedStyles'. This 'sub-property' can be seen in Matlab's inspector, (inspect(handle)) by expanding the 'MarkerFaceColor' property row.
I would like to do something like the following or get the equivalent of such a command.
allowedstyles = get(hh,'MarkerFaceColorAllowStyles');
Screen shot of Matlab's Inspect window indicating information I seek.
https://drive.google.com/file/d/0B0n19kODkRpSRmJKbkQxakhBRG8/edit?usp=sharing
update:
For completeness my final solution for accessing this information via a cellstr was to write the following function. Thanks to Hoki.
FYI, this information (allowed styles) is useful for a GUI when you want to offer user choices for a property such as MarkerFaceColor, where you don't know the type of graphics object they are modifying. I populate a listbox with these 'allowedStyles' along with an option to set a colour. Mesh plot 'MarkerFaceColor' allows styles {'none','auto','flat'}, while a lineseries plot has {'none','auto'}.
function out = getAllowedStyles(hh,tag)
% hh - handle returned from plot, surf, mesh, patch, etc
% tag - the property i.e. 'FaceColor', 'EdgeColor', etc
out = [];
try
aa = java(handle(hh(1)));
bb = eval(sprintf('aa.get%s.getAllowedStyles;',tag));
bb = char(bb.toString);
bb(1) = []; bb(end) = [];
out = strtrim(strsplit(bb,','));
end
end
I think it is indeed ReadOnly (or at least I couldn't find the correct way to set the property, but it is definitely readable.
You need first to access the handle of the underlying Java object, then call the method which query the property:
h = plot([0 1]) ; %// This return the MATLAB handle of the lineseries
hl = java(handle(h)) ; %// this return the JAVA handle of the lineseries
allowedstyles = hl.getMarkerFaceColor.getAllowedStyles ; %// this return your property :)
Note that this property is actually an integer index. Your inspect windows translate it to a string saying [none,auto] while in my configuration even the inspect windows only shows 1.
If you want the exact string translation of other values than one, you can call only the parent method:
hl.getMarkerFaceColor
This will display the allowed style in plain text in your console window.
ans =
com.mathworks.hg.types.HGMeshColor#28ba43dd[style=none,allowedStyles=[none, auto],red=0.0,green=0.0,blue=0.0,alpha=0.0]
If you insist on getting this property as a string progamatically, then you can translate the above using the toString method.
S = char( hl.getMarkerFaceColor.toString )
S =
com.mathworks.hg.types.HGMeshColor#1ef346e8[style=none,allowedStyles=[none, auto],red=0.0,green=0.0,blue=0.0,alpha=0.0]
then parse the result.

How to define custom color shortcuts (like 'r', 'g', 'b', 'k' etc) in MATLAB

I wonder whether is it possible to define custom color shortcuts in MATLAB.
It is possible to use r instead of specifying [1,0,0] in MATLAB? Similarly, is it possible to define another shortcut?
For example, I would like to define,
[0.9047,0.1918,0.1988] as rr
[0.2941,0.5447,0.7494] as bb etc
Simply put: Yes and no. You can create custom colour shortcuts like you have said, but the only way I can see you creating these shortcuts is by associative arrays / dictionaries. This may not be what you originally intended, but this is the closest thing I can think of to achieve what you're looking for. You can't create strings like r that will resolve itself to the tuple of [1,0,0] (... at least not to my knowledge), but you can create a dictionary of colour tuples where you access the dictionary by a single character or a string of characters, and the output would be a 3 element array.
With this, use a containers.Map object, and the key type would be a string (like rr, bb, etc.), and the output (value type) would be a double array. As an example, let's say your array was called colourMap. You would then initialize it, and throw some entries in like so:
%// Initialize associative array
colourMap = containers.Map('KeyType', 'char', 'ValueType', 'any');
%// Put some entries in - referring to your post
colourMap('r') = [1 0 0];
colourMap('rr') = [0.9047,0.1918,0.1988];
colourMap('bb') = [0.2941,0.5447,0.7494];
Once you set this up, you can access the specific colour tuple you want by doing:
colourMap(s)
s would be the string that you want. I don't know what you want to use this for, but I'm assuming you may want to customize the colour of a plot. For example, we can do this:
plot(1:5, 1:5, 'Color', colourMap('bb'));
This will create a plot from 1 to 5 for both x and y, and colour the map with the colour tuple stored in bb.
This is the only way I can see you creating customized string shortcuts. FWIW, MATLAB already has built-in colours that you can use to plot your data. For example, if you wanted to plot a red line, you would simply do:
plot(1:5, 1:5, 'r');
Use struct.
%Defining your colors and some other colors in a struct
c = struct('rr', [0.9047, 0.1918, 0.1988], ... %Your required color
'bb', [0.2941, 0.5447, 0.7494], ... %Your required color
'um', [0.0824, 0.1294, 0.4196], ... %ultra marine
'br', [0.6510, 0.5725, 0.3412], ... %bronze
'gl', [0.8314, 0.7020, 0.7843] ); %greyed lavender
Now to use these colors, use the Color property i.e.
plot(x, y, 'Color', c.br); %Using the color 'bronze' defined in the struct

How to change the line properties of a plot in switch environment within a for-loop in Matlab

I have data from simulations in one single .dat file. Depending on certain criteria ('bu') that is contained in one column of the file (#13 here), I want to plot the data with different markers, while also defining the markersize and markerface properties.
What I have is a switch environment for the different cases - defining which markers and properties I want, and all this in a for-loop, to go through all simulation data.
I've tried the following:
for i=1:s1(1)
bu = data1(i,13);
switch bu
case 1
set(h,'kd','MarkerSize',14,'MarkerFaceColor','k');
case 2
set(h,'kd','MarkerSize',14);
case 3
set(h,'k>','MarkerSize',14,'MarkerFaceColor','k');
case 4
set(h,'ks','MarkerSize',14,'MarkerFaceColor','k');
case 5
set(h,'ks','MarkerSize',14);
case 6
set(h,'ko','markersize',14);
case 7
set(findobj(gca,'k^','MarkerSize',14,'MarkerFaceColor','k'));
end
figure(1);
h=plot(Re1(i),A1(i)); hold on
end
First I tried to use a handle 'h', but it said it was undefined, I guess since the h=plot comes later. Then I tried findobj in the last case (which is the case for the first simulation, so this gives the error in the first round), didn't work either ("Incomplete property-value pair" - not sure what it means here).
I also tried putting all these properties in a string like
str=['kd','MarkerSize',14,'MarkerFaceColor','k']
then plot with
h=plot(Re1(i),A1(i),str); hold on
but it doesn't work with/without brackets either.
Now I don't have any further ideas, thankful for any suggestions!
I think the easiest change for you is to put the plot options in a cell array in the switch block. For example:
options = {'kd', 'MarkerSize', 14, 'MarkerFaceColor', 'k'};
Later, when you plot:
plot(x, y, options{:})
Another way I've done it is to set variables and use them in the plot command:
style = 'kd';
markerSize = 14;
markerFaceColor = 'k';
plot(x, y, style, 'MarkerSize', markerSize, 'MarkerFaceColor', markerFaceColor);
There are few different ways to do that, one of them - create all plot objects before hand and then fill them with both data and formatting:
figureHandle = figure;
for i=1:s1(1)
plotHandle(i) = plot(0,0); %just creating valid handle for future here
end;
code above before your for loop with bu switch, and then in your switch
set(ph(i),'kd','MarkerSize',14,'MarkerFaceColor','k', 'Xdata', Re(1), 'Ydata', A1(i));
Approach with str would work too, except you would need two cell arrays - option nad value like that:
firstoption = 'kd';
option = {'MarkerSize','MarkerFaceColor'};
value = {14,'k'};
h=plot(Re1(i),A1(i),firstoption);
for i=1:length(option)
set(h,option{i},value{i});
end;