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.
Related
What is a proper way to separate the initialization and the display of plots in Matlab? (I mean plots in a wide sense here; could be plot, plot3, scatter etc.) To give a concrete example, I have a pretty complex 3D visualization that uses sphere and mesh to draw a static sphere mesh and then scatter3 to plot a moving trajectory on the sphere. To be able to do this in real time I have implemented some simple optimizations, such as only updating the scatter3 object each frame. But the code is a bit messy, making it hard to add additional features that I want, so I would like improve code separation.
I also feel like it might sometimes be useful to return some kind of plot object from a function without displaying it, for example to combine it with other plots in a nice modular way.
An example of what I have in mind would be something like this:
function frames = spherePlot(solution, options)
% Initialize sphere mesh and scatter objects, configure properties.
...
% Configure axes, maybe figure as well.
...
% Draw sphere.
...
if options.display
% Display figure.
end
for step = 1:solution.length
% Update scatter object, redraw, save frame.
% The frames are saved for use with 'movie' or 'VideoWriter'.
end
end
Each step might also be separated out as a function.
So, what is a neat and proper way to do stuff like this? All documentation seems to assume that one wants to display everything right away.
For example
% some sample data
N = 100;
phi = linspace(-pi, pi, N);
theta = linspace(-pi, pi, N);
f = #(phi, theta) [sin(phi).*cos(theta); sin(phi).*sin(theta); cos(phi)];
data = f(phi, theta);
% init plot
figure(1); clf
plot3(data(1,:), data(2,:), data(3,:)); % plot path, not updated
hold on
p = plot3([0 data(1,1)], [0 data(2,1)], [0 data(3,1)]); % save handle to graphics objects to update
s = scatter3(data(1,1), data(2,1), data(3,1), 'filled');
axis equal
xlabel('x'); ylabel('y'); zlabel('z');
t = title('first frame'); % also store handle for title or labels to update during animation
% now animate the figure
for k = 1:N
p.XData = [0 data(1,k)]; % update line data
p.YData = [0 data(2,k)];
p.ZData = [0 data(3,k)];
s.XData = data(1,k); % update scatter data
s.YData = data(2,k);
s.ZData = data(3,k);
t.String = sprintf('frame %i', k); % update title
drawnow % update figure
end
Basically you can update all values for a graphics handle, in this case 'p' and 's'. If you open the matlab doc for plot or plot3 you will find a link to all properties of that primitive: e.g. Line Properties. Similar documentation pages exist for scatter/imagesc etc.
So the general idea is to first create a figure with the first frame, save the handles to the objects you would like to update (p = plot(...), and then enter a loop in which you update the required property of that graphics object (e.g. p.Color = 'r', or p.XData = ...).
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 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.
Imagine I have function myPlot which creates a simple plot and returns figure and axes handle:
function [ fig_handle, axes_handle ] = myPlot( myTitle )
x = linspace(-10,10,100);
y = x.^2;
fig_handle = figure;
plot(x,y,'Color','b')
ylim([0,42]); xlim([-42/10,42/10]);
ylabel('y'); xlabel('x');
title(myTitle);
grid on;
axes_handle = gca;
end
Now I want to call that function multiple times with different input parameters and concatenate them into an array of subplots. The solution I came up with is
[f1,a1] = myPlot('Plot #1');
[f2,a2] = myPlot('Plot #2');
figure(3)
s221 = subplot(211);
s222 = subplot(212);
copyobj(get(a1,'children'),s221)
copyobj(get(a2,'children'),s222)
it gives me
so the new 2-part plot does not keep any property of the two plots before. Of course I am aware that I can just do:
set(s221,'Ylabel',get(a1,'Ylabel'))
with all properties. But I try to avoid this. Is there something easier I am missing?
Another workaround is to copy the full axes object (so all the properties come along) into a new figure. The only property you have left to set yourself is the position according to the rules of subplot(xyz).
To start with your example :
[f1,a1] = myPlot('Plot #1');
[f2,a2] = myPlot('Plot #2');
Then copy the axis of each figure into the new figure
h4 = figure(4)
copyobj(get(f1,'children'),h4)
copyobj(get(f2,'children'),h4)
They are both here, but they are still superimposed. You just have to reposition them now.
For a quick workaround I use an intermediate figure to let subplot calculate the position for me but these positions (axes arrangement) could probably be calculated by yourself with a helper function (rip some code from subplot.m if you need).
%// Just to get some position calculated for me
figure(3)
s221 = subplot(211);
s222 = subplot(212);
Then I apply the positions to axes in the figure:
hl = flipud( get(h4,'Children') ) ;
set( hl(1),'Position', get(s221,'Position') )
set( hl(2),'Position', get(s222,'Position') )
Just be careful, the axes/children list is upside down (so the use of the flipud function), but if you put that in a loop, you could just run the loop backward.
Edit
If you are going to discard the original figure (f1 and f2), then you can also simply move the axes to the new figure (instead of copying them) by assigning the Parent property of the axes, then close the figure (to keep things tidy). Just use :
set(a1,'Parent',h4) ; close(f1)
set(a2,'Parent',h4) ; close(f2)
instead of the 2 lines with copyobj. The rest of the solution is identical. Not sure if there is a performance gain by moving an object instead of replicating it (Matlab may do a copy in the background anyway ... or not) but if it involve many figures with heavy data sets it may at least save some memory during the process.
One possible workaround could be the following, not really generic though. Would be interested in a solution solving the original problem.
If it is possible to modify the myPlot function one could pass the figure handle and subplot specifier and plot everything correctly from the beginning.
function [ fig_handle, axes_handle ] = myPlot(fig_handle, sub, myTitle )
figure(fig_handle); subplot(sub)
....
end
called by
f = figure(3);
[f,a1] = myPlot(f, 221,'Plot #1');
[f,a2] = myPlot(f, 222,'Plot #2');
Take the follow code for example:
Hsp=subplot(1,2,1);
image(rand(5,5));
Hc=colorbar;
subplot(1,2,2);
image(rand(5,6));
colorbar;
My question is how to obtain Hc, given only Hsp.
As is known, the type of a colorbar is axes. So I tried to search all the children of the subplot.
Hs=findall(Hsp,'type','axes');
But, there is no value in Hs which matches Hc.
Using the following script can find the handle of all colorbars which are children of an axes. Here Ha1 is the handle of the axes with image (e.g. a subplot), Hc1s are the handles of the peer colorbars of the axes.
function Hc1s = find_peer_colorbars_of_an_axes(Ha1)
Hf = get(Ha1,'parent');
Haxs = findobj(Hf,'type','axes');
IsC=false(1,length(Haxs));
Hc1s=[];
for i=1:length(Haxs)
if isa(handle(Haxs(i)),'scribe.colorbar');
H=handle(Haxs(i));
if isequal(double(H.axes),Ha1)
Hc1s=[Hc1s,Haxs(i)];
end
end
end
Your colorbars are children of the figure, not of your subplot axes (colorbars are themselves axes). Try
hc = get(hf, 'children')
to get a list of all children of the figure, where hf is the figure handle. I'm not sure how you would which element of hc is equal to your Hc, i.e. which is the first colorbar.
Edit:
If you need to use an object's handle later on, it is best to assign it to a variable when it is created and to use that variable throughout.
However, if you don't want to do this (although I strongly recommend that you do) I can think of two things you can do. They are not particularly elegant and are definitely more work that just assigning your object handle to a variable.
If you know the order in which the axes were created then you are in luck: in the list if children, the first child created is the last element in the list and the last child created is the first. For example,
hf = figure;
ha1 = subplot(1,2,1);
image(rand(5,5));
hc1 = colorbar;
ha2 = subplot(1,2,2);
image(rand(5,5));
hc2 = colorbar;
hcs = get(hf, 'children')
hcs =
206.0016
204.0011
176.0016
174.0011
[hc2, ha2, hc1, ha1]'
ans =
206.0016
204.0011
176.0016
174.0011
Since you want the first colorbar, which was the second child created, you can then use
hc(end-2)
Alternatively, when creating the colorbar which you want to refer to in the future, set it's tag property. In the above example, replace the line
hc1 = colorbar;
with
hc1 = colorbar('tag', 'myID');
You can then get the handle to this object later with
findobj(hf, 'type', 'axes', 'tag', 'myID')