Making a legend when some of the data sets could be empty - matlab

For my project I have six sets of data to put on a scatter plot, like so:
plot(ax, ay, '.r', bx, by, '.b', cx, cy, '.m', dx, dy, '.c', ex, ey, '.y', fx, fy, '.k');
Sometimes these sets of data will be empty, so bx and by might have nothing in them, therefore getting skipped over.
Is there any way to build a legend that will match the right label to the right color piece of data? In other words, data in the [cx, cy] would always match the label 'c' on the legend next to a magenta dot, even when there is no 'b'. My current legend is as follows:
legend('a', 'b', 'c', 'd', 'e', 'f', -1);
Thanks!

You can get the results you want if you first replace any sets containing empty data with NaN values. For example:
x = [];
y = [];
if isempty(x) || isempty(y)
x = nan;
y = nan;
end
plot(1:10, rand(1,10), 'r', x, y, 'g', 1:10, rand(1,10), 'b');
legend('r', 'g', 'b');
Leaving x and y as empty would give a warning when creating the legend and result in an incorrect legend. When passing empty vectors ([]) to the plot command, it simply doesn't create a line object for that data. When passing a NaN value, it creates but doesn't render a line object, so it still exists and can have a legend entry generated for it.

To complete #gnovice answer with the advantage proposed in #Guto answer, if the order of the lines matters, you can still use NaN, and set their order after it:
x = 1:10;
% plot and get the handle to the lines:
p = plot(x,x,'--g',nan,nan,'r',x,x,'b','linewidth',2);
% set the order of the lines from front to back:
set(p(1).Parent,'Children',p(1).Parent.Children([3 2 1]))
% add the legend:
legend('data 1', 'data 2', 'data 3');
In the above example we bring the green line on top of the blue one:

One other possibility that does not use the x=NaN; is to use a 'dummy plot' out of the boundaries.
The drawback of this process is that you need to select the boundaries manually and if you made any changes in the main plots, you need to manually change the dummy plot as well. Bad idea for use in automated plots. Also If the legend is deleted and called again (either in the insert menu or by legend off; legend show)
The advantage is that you can plot it in a different order from the legend. This can be important when plotting multiple line types and thickness that overlay in some regions. In the figure below, for instance, if you plot the green first, it will disappear in the region x<5 under the blue line.
An example of code:
x_e = [];
y_e = [];
figure()
hold on
plot(100,100, '--g', 100,100, 'r', 100,100, 'b'); %dummy plot
x=1:10;
y=[1 3 3 3 3 4 5 6 9 10]/10;
y2=[1 3 3 3 3 7 6 6 4 3]/10;
plot(x,y2, 'b',x_e, y_e, 'r',x, y, '--g','linewidth',2);
set(gca,'box','on',... %box just to be prettier
'Xlim',[1 10], 'Ylim',[0 1]) % relevant set up!
legend('data 1', 'data 2', 'data 3');
and it give this graph:

Related

Four identical subplots with different view angle

I have made the following 3D plot:
figure
subplot(2,1,1)
hold on
plot3(X_LE, Y_LE,Z, 'red', 'linewidth', 2)
plot3(X_TE, Y_LE,Z, 'red', 'linewidth', 2)
plot3(X_LE, -Y_LE,Z, 'red', 'linewidth', 2)
plot3(X_TE, -Y_LE,Z, 'red', 'linewidth', 2)
plot3([X_LE(end) X_TE(end)],[Y_LE(end) Y_LE(end)],[0 0], 'red', 'linewidth', 2)
plot3([X_LE(end) X_TE(end)],[-Y_LE(end) -Y_LE(end)],[0 0], 'red', 'linewidth', 2)
grid on
axis equal
xlabel('x/b','Interpreter','latex')
ylabel('y/b','Interpreter','latex')
view(-45, 23);
However, I would like to create a 2x2 subplot where in each one of the 4 subplots the view angle will be different.
Instead of copying the whole code 4 times and just changing the view angle, is there some elegant way to do it?
Example of the output im trying to get:
You could make use of the copyobj function.
copyobj will allow you to replicate any graphic object you already defined. So the principle is to create your first subplot, then simply copy it 3 times and adjust the position and view of each new copy.
To use this feature (and for many other reasons), it is good to save the handle of the graphic objects you create. This is usually done by assigning the return value of a graphic instruction to a variable. Ex:
hp = plot(x,y) ;
Would keep the handle of the plot object in the variable hp, so you can always use this handle to modify the line properties.
For your specific case it would go like that:
%% Quick mock up of a 3D triangle (you did not give any sample data)
x = [0 ; 2 ; 1 ; 0 ] ;
y = [3 ; 1 ; 5 ; 3 ] ;
z = [2 ; -1 ; 4 ; 2 ] ;
%% use dummy subplots, just to save their position on a figure
hf = figure ;
for k=1:4
hs = subplot(2,2,k) ;
axpos{k,1} = hs.OuterPosition ;
end
clf(hf) ; % clear all subplots, keep only "axpos" and the empty figure
%% Generate the first subplot
%% (use your own code for that, but don't forget to retrieve the handles of the figure and the axes)
figure(hf) ;
% hs(1) = subplot(2,2,1) ; % use the line below instead. It is equivalent
% and it also set the 'hold on' mode for the axe
hs(1) = axes('parent',hf, 'OuterPosition',axpos{1},'NextPlot','add') ;
hp = plot3(x,y,z,'red', 'linewidth', 2) ;
grid on
axis equal
xlabel('x/b','Interpreter','latex')
ylabel('y/b','Interpreter','latex')
view(-45, 23);
%% Now use "copyobj" to copy the full axes object with the content and labels
for k=2:4
hs(k) = copyobj( hs(1) , hf ) ; % create a copy of the full subplot
set( hs(k) , 'OuterPosition',axpos{k} ) % reposition it so it doesn't overlap the original
end
Then all you have to do is to change the view of each subplot according to your needs.
This can be done by using the subplot handle as the first argument of the view instruction. For ex:
%% adjust the view of each subplot
view( hs(2) , 25,40)
view( hs(3) , -25,32)
view( hs(4) , 37,92)
Note: If you know the view you want beforehand, you could also place the values in an array at the beginning, and set each axes view directly in the loop where you adjust their position.
Yes, an elegant solution would be creating a function from your code, like this.
function [y] = changeViewAngle(pos, azimuth, elevation)
X_LE = -1:0.01:1;
X_TE = -1:0.01:1;
Y_LE = -1:0.01:1;
Z = -1:0.01:1;
subplot(2,2,pos)
hold on
plot3(X_LE, Y_LE,Z, 'red', 'linewidth', 2)
plot3(X_TE, Y_LE,Z, 'red', 'linewidth', 2)
plot3(X_LE, -Y_LE,Z, 'red', 'linewidth', 2)
plot3(X_TE, -Y_LE,Z, 'red', 'linewidth', 2)
plot3([X_LE(end) X_TE(end)],[Y_LE(end) Y_LE(end)],[0 0], 'red', 'linewidth', 2)
plot3([X_LE(end) X_TE(end)],[-Y_LE(end) -Y_LE(end)],[0 0], 'red', 'linewidth', 2)
grid on
axis equal
xlabel('x/b','Interpreter','latex')
ylabel('y/b','Interpreter','latex')
view(azimuth, elevation)
end
and then saving it as a file with the same name, i.e. changeViewAngle.m
Now create another script, main.m , which looks as below,
figure(2);
clear;
clc;
clf;
changeViewAngle(1, -45, 23)
changeViewAngle(2, 45, -23)
changeViewAngle(3, 25, 90)
changeViewAngle(4, 35, 75)
Note: Remember to change the directory to where you saved your both files. It would be convenient if you saved them both the same folder. Otherwise, MATLAB might complain that it cannot find the function.
Of course, you will also have to change the values for Z, X_LE, X_TE and Y_LE, according to the plot you want to make. I did not have those values, so I used some dummy values in this function. But I guess you understand how to plot 4 subplots with 4 different viewing angles, as that was the main point of your question.

matlab adding single legend for multiple data

figure(1);
hold on;
na=4;
circle_X = [0 0 5 5]';
circle_Y = [0 3 0 3]';
for a = 1:na
r=0.3;
N=100;
theta=linspace(0, 2*pi, N);
cx=r*cos(theta)+circle_X(a);
cy=r*sin(theta)+circle_Y(a);
plot3(cx , cy, 300*ones(N), 'r', 'linewidth', 2,'DisplayName',sprintf('circle'));
end
legend('show');
I want to draw 4 circles and add a single legend 'circle' indicating 4 circles all at once, without using "legend('circle')".
For now the legend looks like this
how should I change the code?
First, you don't need plot3, you can achieve the same figure with plot, and probably the long legend is because of that.
just change the line of plot to that:
plot(cx , cy, 'r', 'linewidth', 2,'DisplayName','circle');
Now, the legend will have 4 entries because you draw four objects. If you want a single entry you have some ways:
add the legend inside the loop, after an if statement. For example,
if a==1 ,
legend('show');
end
get handles for your plots, and legend just one of them. It can be done directly from legend, but then you will need to specify the string:
for....
h(a)=plot...
end
legend(h(1),'circle')
get handles like in part 2, and if you don't want specify the string 'circle', you can use the undocumented hasbehavior:
for...
h(a)=plot...
end
hasbehavior(h(2),'legend',false);
hasbehavior(h(3),'legend',false);
hasbehavior(h(4),'legend',false);
l=legend('show');

MATLAB: how to customize non linear X axis (for example ticks at 1,2,3,4,5,20,100,'string')

I'm using the MATLAB plot feature to compare two vectors. I would like my X axis to represent 1 through 7, and then 14, 21, and then a category at the end for points with undetermined X values..(I'm also not sure how to represent these numberless point (they have Y values, just no X values) I could assign a large number outside any of my X values (1000) to these points, do the 1-7,14,21,1000 and then change the 1000 label to my 'string for un-numbered points'. ??
Here is a way to do it based on the example by The Mathworks here.
The trick is to create 2 x axis with different ranges and make one of them transparent. You can then play around with its properties. I modified a bit their code but kept most of their comments because they explain well the steps.
For the demo I used scatter to represent the points and colored the "good" points in red and the other point (here only 1) in green. You can customize all this of course. For instance I used a value of 100 and not 1000 for the x value of the 2nd axes, but I'll let you figure out how to modify it all to change the output as you wish.
clear
clc
close all
%// Create axes 1 and get its position
hAxes1 = axes;
axes_position = get(hAxes1, 'Position');
%// Create axes 2 and place it at the same position than axes 1
hAxes2 = axes('Position', axes_position);
%// Your data go here.
x = [1 7 14 21 100];
y = rand(1, length(x));
%// Plot the two sections of data on different axes objects
hPlot1 = scatter(hAxes1, x(1:4), y(1:4),40,'r','filled');
hold on
hPlot2 = scatter(hAxes2, x(end), y(end),40,'g','filled');
%// Link the y axis limits and fontsize property of the axes objects
linkaxes([hAxes1 hAxes2], 'y');
linkprop([hAxes1 hAxes2], 'FontSize');
%// Set the x range limits and tick mark positions of the first axes object
set(hAxes1, 'XLim', [1 25], ...
'XTick', [1 7 14 21])
%// Set the x range limits and tick mark positions for the second axes object.
%// Also set the background color to 'none', which makes the background
%// transparent.Add the label for the "super points".
set(hAxes2, 'Color', 'none', ...
'YTickLabel', [], ...
'XLim', [95 100], ...
'XTick', 100,'XTickLabel',{'Super Points'})
And the output:
EDIT
If you wish to add a fit line, I suggest adding a 3rd axes at the same position than the other 2, make it transparent and remove its X- and Y-ticks.
i.e. Add something like this:
hAxes3 = axes('Position', axes_position,'Color','none','YTick',[],'XTick',[]);
And in the call to polyfit/polyval, in my example you want to get only the first 4 elements (i.e. the red ones).
Hence:
p = polyfit(x(1:4),y(1:4),1);
y_poly = polyval(p,x(1:4));
And then plot the line on hAxes3:
hPlot3 = plot(x(1:4),y_poly)
Output:

MATLAB fill area between lines

I'm trying to do something similar to what's outlined in this post:
MATLAB, Filling in the area between two sets of data, lines in one figure
but running into a roadblock. I'm trying to shade the area of a graph that represents the mean +/- standard deviation. The variable definitions are a bit complicated but it boils down to this code, and when plotted without shading, I get the screenshot below:
x = linspace(0, 100, 101)';
mean = torqueRnormMean(:,1);
meanPlusSTD = torqueRnormMean(:,1) + torqueRnormStd(:,1);
meanMinusSTD = torqueRnormMean(:,1) - torqueRnormStd(:,1);
plot(x, mean, 'k', 'LineWidth', 2)
plot(x, meanPlusSTD, 'k--')
plot(x, meanMinusSTD, 'k--')
But when I try to implement shading just on the lower half of the graph (between mean and meanMinusSTD) by adding the code below, I get a plot that looks like this:
fill( [x fliplr(x)], [mean fliplr(meanMinusSTD)], 'y', 'LineStyle','--');
It's obviously not shading the correct area of the graph, and new near-horizontal lines are being created close to 0 that are messing with the shading.
Any thoughts? I'm stumped.
You may be getting a problem with using mean as a variable, since it's also a reserved MATLAB command. Try clearing the variable space and then using a unique variable name.
As for the second problem, you want
fill( [x fliplr(x)], [meanUniqueName fliplr(meanMinusSTD)], 'y', 'LineStyle','--');
You also don't need to do this in two steps, but can do it all at once. A code snippet from a script I'm currently working on does the exact same thing and contains the lines:
avar = allan(DATA, tau);
xFill = [avar.tau1 fliplr(avar.tau1)];
yFill = [avar.sig2+avar.sig2err fliplr(avar.sig2-avar.sig2err)];
figure(2);
fill(xFill,yFill,'y','LineStyle','--')
line(avar.tau1,avar.sig2);
So I fill the area between the two error lines, and then draw the data line on top.
It turned out to be a column vs row vector issue. For some reason using the fill method above with flipud with the original column vectors doesn't work, but transposing the original variables then using fliplr does. Go figure. Here's the code in case it helps someone else:
x = linspace(0,100, 101);
mean = torqueRnormMean(:,DOF)';
meanPlusSTD = torqueRnormMean(:,DOF)' + torqueRnormStd(:,DOF)';
meanMinusSTD = torqueRnormMean(:,DOF)' - torqueRnormStd(:,DOF)';
fill( [x fliplr(x)], [meanPlusSTD fliplr(meanMinusSTD)], 'k');
alpha(.25);
plot(x, mean, 'k', 'LineWidth', 2)
plot(x, meanPlusSTD, 'k')
plot(x, meanMinusSTD, 'k')
Note that I removed the dotted line and just used thin vs. thick lines to denote standard deviation and mean. I did this because the line style was inconsistent. This code is in a loop where DOF runs from 1:9, and in some of the subplots both std curves would be dashed and in some just the top or bottom. It didn't matter enough to me to have them dashed so I kept it simple. Now this is an example of the graph I get:
One thing that you appear to be doing wrong is that you're applying fliplr to column vectors. That will have no effect. The example you cited uses row vectors. You also concatenate them into a matrix instead of into a single vector like the example. I think that the equivalent with column vectors would be:
fill( [x;flipud(x)], [mean;flipud(meanMinusSTD)], 'y');
Another possibility:
x = 1:1000; % example x values
y_upper = 5+sin(2*pi/200*x); % example upper curve
y_lower = 2+sin(2*pi/230*x); % example lower curve
bar(x, y_upper, 1, 'b', 'edgecolor', 'b');
hold on
bar(x, y_lower, 1, 'w', 'edgecolor', 'w');
axis([0 1000 0 7])
It uses bar (with unit width and same-color edges) to fill the upper curve, and then a second bar to "remove" (plot in white) the lower part.

Matlab - how to make a custom legend

I have the following picture :
And I would like to make a legend for it. Basically, I want to make a legend for each type of rectangle. In the legend box, I want to mark each color line according to the type of body which it marks:
green line : head
yellow line : torso
purple line : right arm
cyan line : left arm
red line : left leg
blue line : right leg
This is basically custom, because I have more rectangles of each type. How can I do a custom legend and attach it to the figure which draws this picture?
There are 2 ways you could go about this. You could create your squares and then assign them to an hggroup. This way you dont have multiple items for each color. Something like this:
hold on
for ii = 1:4
hb(ii) = plot(rand(1,2), rand(1,2),'color','r');
end
hg = hggroup;
set(hb,'Parent',hg)
set(hg,'Displayname','Legs')
legend(hg)
Or you could create dummy objects, like this:
hold on
for ii = 1:4
hb(ii) = plot(rand(1,2), rand(1,2),'color','r');
end
p = plot([],[],'r');
legend(p,'Legs')
The former is a little more elegant.
I would like to add to dvreed77's answer on using hggroup that for clean legend use, I also needed to set the 'IconDisplayStyle' (Matlab R2014a), such that:
%4 kinds of lines:
n_areas = 4;
n_lines = 10;
%use built-in color map
cmap = hsv(n_areas);
%plot lines and generate handle vectors
h_fig = figure;
hold on
h_lines = zeros(1,n_lines);
for l = 1:n_areas
for k = 1:n_lines
h_lines(k) = plot(rand(1,2), rand(1,2),'Color',cmap(l,:));
end
%Create hggroup and set 'icondistplaystyle' to on for legend
curPlotSet = hggroup;
set(h_lines,'Parent',curPlotSet);
set(get(get(curPlotSet,'Annotation'),'LegendInformation'),...
'IconDisplayStyle','on');
end
%Now manually define legend label
legend('heads','legs','hands','feet')
The simplest way I can think of is to first plot one rectangle of each type and construct a legend for only unique rectangles. Like so:
figure;
hold on;
% unique rectangles
plot(rand(1, 10), 'b');
plot(rand(1, 10), 'g');
% the rest
plot(rand(1, 10), 'b');
plot(rand(1, 10), 'g');
% use normal legend with only as many entries as there are unique rectangles
legend('Blue', 'Green');
You will have many lines of the same color, but a legend only for unique colors.
Just draw legend dots outside the plot:
figure;
plot(-1,-1,'gs',-1,-1,'b^',-1,-1,'ro');
legend('x1','x2','x3','Location','NorthWest');
xlim([0,1]); ylim([0,1]);
To control the appearance of legend entries, plot points that have values which are NaN then pass the objects returned by plot and an array of labels to the legend function (NaN points are not visible in the plot, but appear in the legend).
colors = ["red", "blue"];
labels = ["this is red", "this is blue"];
% We 'plot' a invisible dummy point (NaN values are not visible in plots),
% which provides the line and marker appearance for the corresponding legend entry.
p1 = plot(nan, nan, colors(1));
hold on
p2 = plot(nan, nan, colors(2));
% Plot the actual plots. You can change the order of the next two function calls
% without affecting the legend.
plot([0, 1], [0, 1], colors(1));
plot([0, 1], [1, 0], colors(2));
legend([p1, p2], labels)
If the plots in [p1, p2] are not in the current figure when legend([p1, p2], labels) is called, then it will raise the following error:
Invalid argument. Type 'help legend' for more information.
You can filter plots that are not in the current figure using something like this:
plots_in_figure = findall(gcf,'Type','Line');
plots_for_legend_indices = ismember([p1, p2], plots_in_figure);
plots_for_this_legend = this.plots_for_legend(plots_for_legend_indices);
legend(plots_for_this_legend, labels)