I am using the matlab to plot some project figures, see the blow figure. Now I am trying to cut the legend width so that the line won't look so wide. I tried these command as suggest by Benoit_11:
[~,icons,~,~] = legend(leg,'location','northwest');
hline = icons(2);
linedata = get(hline,'xdata');
newdata = [linedata(1)+0.2 linedata(2)];
set(hline,'xdata',newdata,'linewidth',1)
I am using the for loop to plot these figures because I have multiple figures to analysis at the same time. Now I can change the length of the legend line right now. But I got another problem: if I have different length of legend text, even if I set the same starting point and end point, I will get different length for the line in the end (you can see that from the figures). I tried to modify icon(1) but always got the error. Any suggestions?
There are 2 things you are not doing right with your code (aside the fact that you use size as the handles to the legend...that's risky because size is a built-in function):
1) Calling legend with only 1 argument returns a handle to the legend object and getting its position actually gives you the position of the box enclosing the legend, i.e. the text + the line.
2) Using this line:
p(3) = p(3) - 0.06;
does modify the position, however you would need to set the new position of the legend with something like the following for the changes to be effective:
set(HandleToLegend,'Position',p)
To come back to your question, the trick is to assign many outputs during the call to legend; you can then modify specific elements of the legend object.
Actually we only need 1 of the 4 output arguments, called icons in the docs so I'll stick with the notation. Then, we can get the XData property of the line and modify it as we want. The XData is actually a 2-element vector:
[StartingPoint EndingPoint]
so changing one or the other (or both) will change the length of the line displayed in the legend box.
Here is the whole code with comments; I changed the length and linewidth of the line in the 2nd plot to highlight the changes.
clear
clc
close all
x = 1:10;
y = rand(1,10);
figure;
%// Default case
subplot(1,2,1)
plot(x,y);
legend('First plot','Location','NorthWest');
title('Before','FontSize',18);
%// With modifications
subplot(1,2,2)
plot(x,y);
title('After','FontSize',18);
%//========================
%// Change the legend here
%//========================
%// The "icons" output is what you want
[~,icons,~,~] = legend('First plot','Location','NorthWest');
%// icons(1) is the text of the current element in the legend Here its 'First plot'
i_1 = get(icons(1)); %// access the properties with this command.
%// icons(2) is the line associated with that text. Here the blue line.
i_2 = get(icons(2));
%// Mhh I don't know what icons(3) represents haha sorry about that.
i_3 = get(icons(3));
%// Get the actual line
hline = icons(2);
%// Fetch its XData property
LineData = get(hline,'XData')
%// Play with those 2 elements to see the output change.
NewData = [LineData(1)+.2 LineData(2)-.01];
%// Apply the changes
set(hline,'XData',NewData,'LineWidth',3)
Which gives the following:
You need to set the value of the Position property, you can just change the vector p. p does not affect the plot at all, it's just a vector of numbers. You have to modify it, then apply it back to the plot, using
set(size,'Position',p)
There does seem to be a minimum width of the legend however.
Related
I want my legend to include the line from the plot and the marker from the scatterplot. For example,
rest = importdata('test.xlsx');
x = test.data(:,1);
y = test.data(:,2);
xx = min(x):0.001:max(x);
yy = interp1(x,y,xx,'cubic');
figure
s1 = scatter(x,y, 'filled', 'k');
hold on
p1 = plot(xx,yy, '--k');
legend(p1, 'x1');
This code creates the legend with only the dashes from the plot and not points from the scatterplot. I would like the legend to have the both the point and the dashed line at the same label. Something like "-.-"
Any help is much appreciated.
Thanks.
Option 1
Make a dummy plot with no data (nan) for the legend (also, as you can see here you can plot all the elements with one call to plot:
p = plot(nan,nan,'--ok', xx,yy,'--k', x,y,'ok');
set(p,{'MarkerFaceColor'},{'k'}); % fill the circles
legend('x1');
The result:
Option 2
Insted of legend(p1, 'x1');, write this:
[~,ico] = legend(p1,'x1'); % create the legend, and get handels to it's parts
ico(3).Marker = 'o'; % set the marker to circle
ico(3).MarkerFaceColor = 'k'; % set it's fill color to black
ico is:
3×1 graphics array:
Text (x1)
Line (x1)
Line (x1)
The first element is the text 'x1' in the figure. The second element is the dashed line, and the third is the (not-exist) marker of p1. This third element is reserved for cases like plot(xx,yy,'--ok'); where the legend include both marker and a line, but the line (in the legend) is represented with two points and the marker with only one, so we need different objects for them. Try to see what happens if you type ico(2).Marker = 'o'; in the example above.
Legend in MATLAB is additional axes that contains the same primitive object like lines and text.
If you want to draw custom legend the simple way will be using primitive commands line, text and patch for rectangles with filling. Also you can add one more axes object as a container.
By specifying p1 in the legend command you are telling MATLAB to only insert an item in the legend for the line corresponding to handle p1 - which is what you are seeing.
In your example case you just want
>>legend({'label_for_scatter','label_for_plot'});
I have made a simulation that calculates trajectories of objects and plot it.
The figure looks like this:
figure(1)
plot(ArrayRT1,ArrayRT2);
hold on
plot(ArrayRD1,ArrayRD2);
plot(ArrayRM1,ArrayRM2);
title('Interception Trajectory')
xlabel('Downrange (Kft)')
ylabel('Altitude (Kft) ')
grid on
Where:
plot(ArrayRT1,ArrayRT2) - 1st object trajectory
plot(ArrayRD1,ArrayRD2) - 2nd object trajectory
plot(ArrayRM1,ArrayRM2) - 3rd object trajectory
Now, I run the same simulation without closing the figure with different initial conditions to check how they affect the trajectories so basically after the 2nd run I will have 6 lines on my graph.
How can I choose when I make legend to show legend only for 4 lines:
1,2,3 from first run and the 6th (the 3rd from the 2nd run)
Thank you.
Option 1
Use the syntax legend(subset,___) to set a legend only to specific objects in you axes. This requires getting the handles to all these objects. You can do that by assigning then to an array of handles, as in the following example:
x = 1:10;
% plotting all the lines:
figure(1)
hold on
p(1) = plot(x,2*x);
p(2) = plot(x,3*x);
p(3) = plot(x,4*x);
p(4) = plot(x,2*x+1);
p(5) = plot(x,3*x+1);
p(6) = plot(x,4*x+1);
hold off
% set the legend to a subset of the lines
legend(p([1:3 6]),{'Line 1', 'Line 2','Line 3','Line 6'})
Alternatively, you can 'tag' the lines to whom you want to attach a legend and use findobj to locate their handles, as done in option 2 below.
Option 2
You can set the property DisplayName for your plots to something like "no legend" (or any other string) and then use a loop to turn it off for these specific plots. Here is an example:
x = 1:10;
% plotting all the lines:
figure(1)
hold on
plot(x,2*x,'DisplayName','Line 1');
plot(x,3*x,'DisplayName','Line 2');
plot(x,4*x,'DisplayName','Line 3');
plot(x,2*x+1,'DisplayName','no legend'); % tag for no legend
plot(x,3*x+1,'DisplayName','no legend');% tag for no legend
plot(x,4*x+1,'DisplayName','Line 6');
hold off
% set the legend off for all lines with 'no legend'
set_leg_off = findobj('DisplayName','no legend');
for k = 1:numel(set_leg_off)
set_leg_off(k).Annotation.LegendInformation.IconDisplayStyle = 'off';
end
% show the legend
legend show
Note that:
You don't need to set the DisplayName for all the lines, only for those you want to remove from the legend. However, if you just write legend show it will ignore them when counting the data lines, so if you omit the DisplayName only for line 6, it will give it the label "data1".
You can use other property like tag to mark the non-legend lines (or any other property that will distinguish between the line you want to plot and those you don't), and then if you decide later to show them they won't appear with the label "no legend". Just remember to correct the findobj call to the property you use.
Keep in mind that changing object's tag or DisplayName does not effect the appearance of them in the legend, this is only a way to mark them for the findobj function, so you can loop only on them and turn the legend off. If you want to turn the legend on later, you need to use this loop again.
In both cases, the result is:
I have a MATLAB subplot figure. I need the YLabels to left align justify. To do this I am setting the Position property for each ylabel. My problem is the subplots are being created programmatically and therefore I don't know what to set the position as.
In MATLAB I want to use the longest/widest YTickLabel as a reference point for positioning. To do that I want to get the length of each label. I am able to get the YTickLabels by doing:
% Set Label format as string
set(gca, 'YTickLabel', num2str(transpose(get(gca, 'YTick'))))
% Get axis YTickLabels
ax = gca;
labels = get(ax, 'YTickLabel');
% Print labels to console
disp(labels)
I would like to iterate through the labels and find the length of the longest label. I've tried accessing them as a cell array but get 'Cell contents reference from a non-cell array object error.' And when I try matrix indexing nothing prints.
Does anyone know if it is possible to get the length of each individual YTickLabel value?
Useful info:
MATLAB R2014b
By "length of each individual YTickLabel value" I understand that you wish to get the number of characters forming each label.
It's quite easy using the numel function, which outputs the number of elements in a cell for an example. Since labels are stored in a cell array, we can use the fancy function cellfun to apply numel to each cell, then convert to a numeric array with cell2mat
In short you can use this:
LabelLength = cell2mat(cellfun(#(x) numel(x),labels,'uni',0))
here is some sample code to illustrate:
clear
clc
close all
x = 1:5;
y = rand(size(x));
scatter(x,y,40,'r','filled')
set(gca,'YTick',[1 3 5],'YTickLabel',{'One';'ThisIsThree';'AndFive'})
grid on
labels = get(gca,'YTickLabel')
LabelLength = cell2mat(cellfun(#(x) numel(x),labels,'uni',0))
and output:
LabelLength =
3
11
7
You could replace cellfun with this equivalent for-loop:
LabelLength = zeros(numel(labels),1);
for k = 1:numel(labels)
LabelLength(k) = numel(labels{k});
end
LabelLength
Note that as a workaround offering quite a lot of flexibility, you could replace the YTickLabels by text objects, for which you can set the HorizontalAlignment property to left for the text to be left-justified.
Hope that helps!
I'm trying to have a textbox in MATLAB on a spinning plot, but I don't want the textbox to change its position relative to the figure. I thought that 'units','normalized' in the text function would do it, but it's not quite working, as the example below illustrates. I suppose I could use uicontrol but I'd like to use Greek letters and I can't get uicontrol looking quite as good as text. The following example recreates my problem. You'll notice the text box moves as the plot spins, but I'd like it to just stay in the top left region where it starts. Thank you!
part_x = rand(1000,3)-.5; %generate random 3D coordinates to scatter
fig1 = figure;
scatter3(part_x(:,1), part_x(:,2), part_x(:,3))
axis equal vis3d
axis([-1 1 -1 1 -1 1])
set(fig1,'color','w')
for tau = 1:150
view(tau+20,30); %spin the plot
pause(.01)
if tau~=1; delete(tau_text); end; %delete the previous text if it exists
tau_text = text(.1,.7,...
['\tau = ',num2str(tau)],...
'units','normalized',... %text coordinates relative to figure?
'Margin',3,... %these last 3 lines make it look nice
'edgecolor','k',...
'backgroundcolor','w');
end
Several things:
1) As you found out - using an annotation object instead of text object is the way to go. The difference is explained very nicely here.
2) You should only create the annotation once and then modify its string instead of deleting and recreating it on every iteration.
Finally:
part_x = rand(1000,3)-.5;
fig1 = figure;
scatter3(part_x(:,1), part_x(:,2), part_x(:,3))
axis equal vis3d
axis([-1 1 -1 1 -1 1])
set(fig1,'color','w')
%// Create the text outside the loop:
tau_text = annotation('textbox',[0.2 0.8 0.1 0.05],...
'string','\tau = NaN',...
'Margin',4,...
'edgecolor','k',...
'backgroundcolor','w',...
'LineWidth',1);
for tau = 1:150
view(tau+20,30);
pause(.01)
set(tau_text,'String',['\tau = ',num2str(tau)]); %// Modify the string
end
Notes:
1) It is interesting to note that #Otto's suggestion of using legend results in the creation of an axes (because this is what a legend object is - an axes with annotation children). You could then position the legend manually, and get its location using either get(gco,'position') (assuming it was the last thing you clicked) or more generally get(findobj('tag','legend'),'position'). Afterwards, whenever you create the legend, you can just set its position to the one you previously got. You could also get rid of the line\marker inside the legend by deleting the appropriate child of type line from the legend, e.g.:
ezplot('sin(x)');
hLeg = legend('\tauex\tau');
delete(findobj(findobj('Tag','legend'),'Type','line'));
hA1 = findobj(findobj('Tag','legend'),'Type','text');
set(hA1,'Position',[0.5,0.5,0],'HorizontalAlignment','center');
It is of course also possible to manipulate the legend's String using its handle (hA1) directly.
2) This post on UndocumentedMatlab discusses the behavior of annotation objects and some undocumented ways to manipulate them.
You could use
legend(['\tau = ',num2str(tau)],'Location','NorthWestOutside')
Thank you Dev-iL! annotation works much better for this purpose than text and the implementation is very similar. And thank you for the advice on modifying the string rather than deleting an recreating it.
Here is the code now, working much better:
part_x = rand(1000,3)-.5; %generate random 3D coordinates to scatter
fig1 = figure;
scatter3(part_x(:,1), part_x(:,2), part_x(:,3))
axis equal vis3d
axis([-1 1 -1 1 -1 1])
set(fig1,'color','w')
tau_text = annotation('textbox',[0.2 0.8 0.1 0.05],...
'string','',...
'Margin',4,... %these last 4 lines make it look nice
'edgecolor','k',...
'backgroundcolor','w',...
'LineWidth',1);
for tau = 1:150
view(tau+20,30); %spin the plot
pause(.01)
set(tau_text,'String',['\tau = ',num2str(tau)]);
end
I got a for i=1:15. Inside I generate a variable d=1:0.01:10, which is the x'x axis and based on this, I create a continuous function F(d) which has 2 unique variables pitch and yaw. I then plot this using different colors in every recursion using cmap = hsv(15);. So then it is:
d=1:0.01:10;
cmap = hsv(15);
for i=1:15
pitch = unidrnd(10);
yaw = unidrnd(10);
for j=1:length(d)
F(j) = d(j)*3*pitch*yaw; %// some long calculation here
end
p1 = plot(d,F,'Linewidth', 1.0);
title ('blah blah')
set(p1, 'Color', cmap(i,:));
hold on;
legend (['pitch,yaw:', num2str(pitch) num2str(yaw)])
end
hold off;
This code updates the unique pitch, yaw values in every recursion (without space between them so it is kind irritating) but fails to:
Apply the proper color, visible in the figure.
Hold the color from the previous iteration and the values of pitch,yaw.
Semidocumented Solution
Adding lines to a legend in a loop can be accomplished with "dynamic legends", as described on undocumentedmatlab.com.
The idea is to replace the legend command with:
legend('-DynamicLegend');
Then update the plot command with a DisplayName parameter:
plot(d,F,'Linewidth',1.0,'DisplayName',sprintf('pitch,yaw: %d,%d',pitch,yaw));
Then plots that are added to the axes get added to the legend:
If semi-documented features are not your cup of tea, use the DisplayName trick and simply toggle the legend off/on. That is, instead of -DynamicLegend:
legend('off'); legend('show');
A different variation that does not use either DisplayName or -DynamicLegend is to delete and recreate the legend with an array of stored strings.
Official Solution
The official solution recommended by MathWorks it so grab the existing legends` line handles and manually update the legend with those handles. This is pretty painful by comparison to the dynamic legend solution above:
% Get object handles
[LEGH,OBJH,OUTH,OUTM] = legend;
% Add object with new handle and new legend string to legend
legend([OUTH;p1],OUTM{:},sprintf('pitch,yaw: %d,%d',pitch,yaw))
As an HG2 (default in R2014+) alternative to #chappjc's official MW solution, one can take advantage of legend being re-implemented as its own class rather than a kludge of other graphics objects. This has cleaned up things a bit so they are simpler to interact with.
Though these new legend objects do not have an exposed property linking legend items to plotted objects, they do have such a property, 'PlotChildren', which is an array of object handles.
For example:
x = 1:10;
y1 = x;
y2 = x + 1;
figure
plot(x, y1, 'ro', x, y2, 'bs');
lh = legend({'Circle', 'Square'}, 'Location', 'NorthWest');
pc = lh.PlotChildren
Returns:
pc =
2x1 Line array:
Line (Circle)
Line (Square)
To update our legend object without calling legend again, we can modify the 'PlotChildren' and 'String' properties of our existing legend object. As long as there is a 'String' entry for each object in 'PlotChildren', it will be rendered in the legend.
For example:
y3 = x + 2;
hold on
plot(x, y3, 'gp');
% To make sure we target the right axes, pull the legend's PlotChildren
% and get the parent axes object
parentaxes = lh.PlotChildren(1).Parent;
% Get new plot object handles from parent axes
newplothandles = flipud(parentaxes.Children); % Flip so order matches
% Generate new legend string
newlegendstr = [lh.String 'Pentagram'];
% Update legend
lh.PlotChildren = newplothandles;
lh.String = newlegendstr;
Which returns:
This functionality can be wrapped into a generic helper function to support appending one or more legend entries. We've done so with legtools on GitHub
As of MATLAB 2017a, legends update automatically when adding or removing graphics objects.
Thus, nothing specific needs to be done now. One creates the legend, then in a loop one can add lines to the axes and they'll automatically appear in the legend.