How to make a matlab legend recognize multiple scatter plots? - matlab

I want to place three scatter plots in the same figure window and have a legend that describes them. The scatter plots all load in the same window just fine, but the legend only recognizes the last series. In other words, the legend shows a red marker (the color for the last series) for each of its entries.
How do I make the legend recognize each scatter and not just the last one? I've tried a bunch of different things, and none of them seem to work. Thanks!
The picture is the plot for one of my datasets, note the legend.
s10 = scatter3(x1, y1, z1, 'b'); hold on;
s1 = scatter3(x2, y2, z2, 'g'); hold on;
s01 = scatter3(x3, y3, z3, 'r'); hold on;
legend([s10,s1,s01], {'10ms', '1ms', '0.1ms'})
% Every legend entry is red (pertains to the last series)

I can't reproduce the problem. Using your code above with random data seems to work (I did fix a typo, you need a comma after the first argument to legend):
x1 = rand(10, 1); y1 = rand(10, 1); z1 = rand(10, 1);
x2 = rand(10, 1); y2 = rand(10, 1); z2 = rand(10, 1);
x3 = rand(10, 1); y3 = rand(10, 1); z3 = rand(10, 1);
s10 = scatter3(x1, y1, z1, 'b'); hold on;
s1 = scatter3(x2, y2, z2, 'g'); hold on;
s01 = scatter3(x3, y3, z3, 'r'); hold on;
legend([s10,s1,s01], {'Series 10', 'Series 1', 'Series 01'})

For me this seems to be associated with the recent version of Matlab (R2015b); in this version I get the same problem as you with the legend entries showing only one color (in contrast to the other answers that can't reproduce the problem). But if I roll back to a previous version (R2010b), the problem goes away. I'm not sure if you have that option, but it might help diagnose the precise issue.

If the previous answer doesn't work, you can also exploit dynamic legends. This answer is what is inspiring this post: Dynamic Legend (Updates in every recursion). This is a rather undocumented feature but it does work very well. Basically, after each plot, you are able to dynamically update what the legend looks like without having to make one call to legend that has all of them together.
As such, try something like this. I'll borrow some of the previous answer's code to get me started:
x1 = rand(10, 1); y1 = rand(10, 1); z1 = rand(10, 1);
x2 = rand(10, 1); y2 = rand(10, 1); z2 = rand(10, 1);
x3 = rand(10, 1); y3 = rand(10, 1); z3 = rand(10, 1);
scatter3(x1, y1, z1, 'b', 'DisplayName', '10ms'); hold on;
legend('-DynamicLegend');
scatter3(x2, y2, z2, 'g', 'DisplayName', '1ms'); hold on;
legend('-DynamicLegend');
scatter3(x3, y3, z3, 'r','DisplayName', '0.1ms'); hold on;
legend('-DynamicLegend');
Call scatter3, then make sure that you use the 'DisplayName' flag and place what you would normally put in the appropriate legend spot. After each call to scatter3 after, you use the legend('-DynamicLegend'); command to signal to MATLAB that the legend entries will be forthcoming... you're going to specify them in the 'DisplayName' flag.
When you do that, this is the figure I get:
As a minor note, I can't reproduce your plot either. I get the same plot as the previous answer.

This issue is caused by a Matlab bug affecting version R2015b. It was fixed in R2016a. There is a bugreport here, which contains a patch and 3 alternative workarounds.
Here are the workarounds in case the link goes dead:
If you are unable to install the patch, there are three alternative workarounds:
If the CData of each scatter plot is an RGB triplet, then assign the MarkerEdgeColor or MarkerFaceColor of each scatter plot to the value of the CData:
s1 = scatter(1:10,1:10);
hold on
s2 = scatter(2:11,1:10);
s1.MarkerEdgeColor = s1.CData;
s2.MarkerEdgeColor = s2.CData;
legend('show');
Assign an RGB triplet to the MarkerEdgeColor or MarkerFaceColor of each scatter plot:
s1 = scatter(1:10,1:10);
hold on
s2 = scatter(2:11,1:10);
s1.MarkerEdgeColor = [0 0.4470 0.7410];
s2.MarkerEdgeColor = [0.8500 0.3250 0.0980];
legend('show');
Use this workaround when all the points within each scatter plot are the same color.
Call the legend function with two or more output arguments:
s1 = scatter(1:10,1:10,[],1:10);
hold on
s2 = scatter(2:11,1:10,[],26:35);
[h, ~] = legend('show');
Use this workaround when the points within each scatter plot are different colors.

Related

How to create three Y-axis in one graph? [duplicate]

I have 4 sets of values: y1, y2, y3, y4 and one set x. The y values are of different ranges, and I need to plot them as separate curves with separate sets of values on the y-axis.
To put it simple, I need 3 y-axes with different values (scales) for plotting on the same figure.
Any help appreciated, or tips on where to look.
This is a great chance to introduce you to the File Exchange. Though the organization of late has suffered from some very unfortunately interface design choices, it is still a great resource for pre-packaged solutions to common problems. Though many here have given you the gory details of how to achieve this (#prm!), I had a similar need a few years ago and found that addaxis worked very well. (It was a File Exchange pick of the week at one point!) It has inspired later, probably better mods. Here is some example output:
(source: mathworks.com)
I just searched for "plotyy" at File Exchange.
Though understanding what's going on in important, sometimes you just need to get things done, not do them yourself. Matlab Central is great for that.
One possibility you can try is to create 3 axes stacked one on top of the other with the 'Color' properties of the top two set to 'none' so that all the plots are visible. You would have to adjust the axes width, position, and x-axis limits so that the 3 y axes are side-by-side instead of on top of one another. You would also want to remove the x-axis tick marks and labels from 2 of the axes since they will lie on top of one another.
Here's a general implementation that computes the proper positions for the axes and offsets for the x-axis limits to keep the plots lined up properly:
%# Some sample data:
x = 0:20;
N = numel(x);
y1 = rand(1,N);
y2 = 5.*rand(1,N)+5;
y3 = 50.*rand(1,N)-50;
%# Some initial computations:
axesPosition = [110 40 200 200]; %# Axes position, in pixels
yWidth = 30; %# y axes spacing, in pixels
xLimit = [min(x) max(x)]; %# Range of x values
xOffset = -yWidth*diff(xLimit)/axesPosition(3);
%# Create the figure and axes:
figure('Units','pixels','Position',[200 200 330 260]);
h1 = axes('Units','pixels','Position',axesPosition,...
'Color','w','XColor','k','YColor','r',...
'XLim',xLimit,'YLim',[0 1],'NextPlot','add');
h2 = axes('Units','pixels','Position',axesPosition+yWidth.*[-1 0 1 0],...
'Color','none','XColor','k','YColor','m',...
'XLim',xLimit+[xOffset 0],'YLim',[0 10],...
'XTick',[],'XTickLabel',[],'NextPlot','add');
h3 = axes('Units','pixels','Position',axesPosition+yWidth.*[-2 0 2 0],...
'Color','none','XColor','k','YColor','b',...
'XLim',xLimit+[2*xOffset 0],'YLim',[-50 50],...
'XTick',[],'XTickLabel',[],'NextPlot','add');
xlabel(h1,'time');
ylabel(h3,'values');
%# Plot the data:
plot(h1,x,y1,'r');
plot(h2,x,y2,'m');
plot(h3,x,y3,'b');
and here's the resulting figure:
I know of plotyy that allows you to have two y-axes, but no "plotyyy"!
Perhaps you can normalize the y values to have the same scale (min/max normalization, zscore standardization, etc..), then you can just easily plot them using normal plot, hold sequence.
Here's an example:
%# random data
x=1:20;
y = [randn(20,1)*1 + 0 , randn(20,1)*5 + 10 , randn(20,1)*0.3 + 50];
%# plotyy
plotyy(x,y(:,1), x,y(:,3))
%# orginial
figure
subplot(221), plot(x,y(:,1), x,y(:,2), x,y(:,3))
title('original'), legend({'y1' 'y2' 'y3'})
%# normalize: (y-min)/(max-min) ==> [0,1]
yy = bsxfun(#times, bsxfun(#minus,y,min(y)), 1./range(y));
subplot(222), plot(x,yy(:,1), x,yy(:,2), x,yy(:,3))
title('minmax')
%# standarize: (y - mean) / std ==> N(0,1)
yy = zscore(y);
subplot(223), plot(x,yy(:,1), x,yy(:,2), x,yy(:,3))
title('zscore')
%# softmax normalization with logistic sigmoid ==> [0,1]
yy = 1 ./ ( 1 + exp( -zscore(y) ) );
subplot(224), plot(x,yy(:,1), x,yy(:,2), x,yy(:,3))
title('softmax')
Multi-scale plots are rare to find beyond two axes... Luckily in Matlab it is possible, but you have to fully overlap axes and play with tickmarks so as not to hide info.
Below is a nice working sample. I hope this is what you are looking for (although colors could be much nicer)!
close all
clear all
display('Generating data');
x = 0:10;
y1 = rand(1,11);
y2 = 10.*rand(1,11);
y3 = 100.*rand(1,11);
y4 = 100.*rand(1,11);
display('Plotting');
figure;
ax1 = gca;
get(ax1,'Position')
set(ax1,'XColor','k',...
'YColor','b',...
'YLim',[0,1],...
'YTick',[0, 0.2, 0.4, 0.6, 0.8, 1.0]);
line(x, y1, 'Color', 'b', 'LineStyle', '-', 'Marker', '.', 'Parent', ax1)
ax2 = axes('Position',get(ax1,'Position'),...
'XAxisLocation','bottom',...
'YAxisLocation','left',...
'Color','none',...
'XColor','k',...
'YColor','r',...
'YLim',[0,10],...
'YTick',[1, 3, 5, 7, 9],...
'XTick',[],'XTickLabel',[]);
line(x, y2, 'Color', 'r', 'LineStyle', '-', 'Marker', '.', 'Parent', ax2)
ax3 = axes('Position',get(ax1,'Position'),...
'XAxisLocation','bottom',...
'YAxisLocation','right',...
'Color','none',...
'XColor','k',...
'YColor','g',...
'YLim',[0,100],...
'YTick',[0, 20, 40, 60, 80, 100],...
'XTick',[],'XTickLabel',[]);
line(x, y3, 'Color', 'g', 'LineStyle', '-', 'Marker', '.', 'Parent', ax3)
ax4 = axes('Position',get(ax1,'Position'),...
'XAxisLocation','bottom',...
'YAxisLocation','right',...
'Color','none',...
'XColor','k',...
'YColor','c',...
'YLim',[0,100],...
'YTick',[10, 30, 50, 70, 90],...
'XTick',[],'XTickLabel',[]);
line(x, y4, 'Color', 'c', 'LineStyle', '-', 'Marker', '.', 'Parent', ax4)
(source: pablorodriguez.info)
PLOTYY allows two different y-axes. Or you might look into LayerPlot from the File Exchange. I guess I should ask if you've considered using HOLD or just rescaling the data and using regular old plot?
OLD, not what the OP was looking for:
SUBPLOT allows you to break a figure window into multiple axes. Then if you want to have only one x-axis showing, or some other customization, you can manipulate each axis independently.
In your case there are 3 extra y axis (4 in total) and the best code that could be used to achieve what you want and deal with other cases is illustrated above:
clear
clc
x = linspace(0,1,10);
N = numel(x);
y = rand(1,N);
y_extra_1 = 5.*rand(1,N)+5;
y_extra_2 = 50.*rand(1,N)+20;
Y = [y;y_extra_1;y_extra_2];
xLimit = [min(x) max(x)];
xWidth = xLimit(2)-xLimit(1);
numberOfExtraPlots = 2;
a = 0.05;
N_ = numberOfExtraPlots+1;
for i=1:N_
L=1-(numberOfExtraPlots*a)-0.2;
axesPosition = [(0.1+(numberOfExtraPlots*a)) 0.1 L 0.8];
if(i==1)
color = [rand(1),rand(1),rand(1)];
figure('Units','pixels','Position',[200 200 1200 600])
axes('Units','normalized','Position',axesPosition,...
'Color','w','XColor','k','YColor',color,...
'XLim',xLimit,'YLim',[min(Y(i,:)) max(Y(i,:))],...
'NextPlot','add');
plot(x,Y(i,:),'Color',color);
xlabel('Time (s)');
ylab = strcat('Values of dataset 0',num2str(i));
ylabel(ylab)
numberOfExtraPlots = numberOfExtraPlots - 1;
else
color = [rand(1),rand(1),rand(1)];
axes('Units','normalized','Position',axesPosition,...
'Color','none','XColor','k','YColor',color,...
'XLim',xLimit,'YLim',[min(Y(i,:)) max(Y(i,:))],...
'XTick',[],'XTickLabel',[],'NextPlot','add');
V = (xWidth*a*(i-1))/L;
b=xLimit+[V 0];
x_=linspace(b(1),b(2),10);
plot(x_,Y(i,:),'Color',color);
ylab = strcat('Values of dataset 0',num2str(i));
ylabel(ylab)
numberOfExtraPlots = numberOfExtraPlots - 1;
end
end
The code above will produce something like this:

non-homogenous grouped data in MATLAB plotyy()

I have to plot 1 line plot and 3 grouped scatter plots in a single plot window.
The following is the code I tried,
figure;
t1=0:0.1:10;
X = 2*sin(t1);
ts = 0:1:10;
Y1 = randi([0 1],length(ts),1);
Y2 = randi([0 1],length(ts),1);
Y3 = randi([0 1],length(ts),1);
plotyy(t1,X,[ts',ts',ts'],[Y1,Y2,Y3],'plot','scatter');
%plotyy(t1,X,[ts',ts',ts'],[Y1,Y2,Y3],'plot','plot');
The following are my questions,
The above code works if I replace 'scatter' by 'plot' (see commented out line), but 'scatter' works only for 1 data set and not for 3. Why?
How to individually assign colors to the 3 grouped scatter plots or plots?
Read the error message you're given:
Error using scatter (line 44) X and Y must be vectors of the same
length.
If you look at the documentation for scatter you'll see that the inputs must be vectors and you're attempting to pass arrays.
One option is to stack the vectors:
plotyy(t1,X,[ts';ts';ts'],[Y1;Y2;Y3],'plot','scatter');
But I don't know if this is what you're looking for, it certainly doesn't look like the commented line. You'll have to clarify what you want the final plot to look like.
As for the second question, I would honestly recommend not using plotyy. I may be biased but I've found it far to finicky for my tastes. The method I like to use is to stack multiple axes and plot to each one. This gives me full control over all of my graphics objects and plots.
For example:
t1=0:0.1:10;
X = 2*sin(t1);
ts = 0:1:10;
Y1 = randi([0 1],length(ts),1);
Y2 = randi([0 1],length(ts),1);
Y3 = randi([0 1],length(ts),1);
% Create axes & store handles
h.myfig = figure;
h.ax1 = axes('Parent', h.myfig, 'Box', 'off');
h.ax2 = axes('Parent', h.myfig, 'Position', h.ax1.Position, 'Color', 'none', 'YAxisLocation', 'Right');
% Preserve axes formatting
hold(h.ax1, 'on');
hold(h.ax2, 'on');
% Plot data
h.plot(1) = plot(h.ax1, t1, X);
h.scatter(1) = scatter(h.ax2, ts', Y1);
h.scatter(2) = scatter(h.ax2, ts', Y2);
h.scatter(3) = scatter(h.ax2, ts', Y3);
Gives you:
And now you have full control over all of the axes and line properties. Note that this assumes you have R2014b or newer in order to use the dot notation for accessing the Position property of h.ax1. If you are running an older version you can use get(h.ax1, 'Position') instead.

Shift the z -value of contour plot in Matlab 2014b

I'm trying to make a figure of a surface plot, and beneath the surface I wish to show the contour lines, but I want the contour to be at z = -1 instead of at the default value 0. I found a previous post about this problem here, but when I try the solution the contour is still at z = 0. Maybe it has something to do with the version of MATLAB I'm using, which is 2014b?
Any ideas on how to make it work?
The code I tried:
%# plot surface and contour
Z = peaks;
surf(Z), hold on
[~,h] = contourf(Z); %# get handle to contourgroup object
%# change the ZData property of the inner patches
hh = get(h,'Children'); %# get handles to patch objects
for i=1:numel(hh)
zdata = ones(size( get(hh(i),'XData') ));
set(hh(i), 'ZData',-10*zdata)
end
So, I couldn't really figure out to do it as proposed in the example I found and posted, but I found a way that works. What I ended up doing was basically this:
figure
hold on
surf(X,Y,Z+1);
contour(X,Y,Z);
zz = get(gca,'ZTick');
set(gca,'ZTickLabel',sprintf('%3.1f\n',zz-1));
This gets me the surf and contour in the same figure, but yields some problems with color mappings.
I figured out how to solve the problem with color mappings the user Kine faced. Note: I've done the following code on MATLAB R2015b:
offset = 0.5;
plotHandle = surfc(X1, Y1, Z1);
hold on;
% This line moves the surface up from its original location and increases the space between the surface and the contour plot
plotHandle(1).ZData = plotHandle.ZData + offset;
% However in doing so the color mappings are changed. So, the line below restores these mappings
plotHandle(1).CData = plotHandle.CData - offset;
% This line changes fills the contour plot
plotHandle(2).Fill = 'on';
grid on;
% The following lines draw critical areas on the contour line, as it was more readable in my case
axisHandle = gca;
ZHeight = axisHandle.ZLim(1);
plot3(X2, Y2, ZHeight, 'o', 'MarkerSize', 10, 'LineWidth', 1, 'Color', 'k', 'MarkerFaceColor', 'm');
plot3(Y2, X2, ZHeight, 'o', 'MarkerSize', 10, 'LineWidth', 1, 'Color', 'k', 'MarkerFaceColor', 'm');
hold off
I got the same problem.
And finally, I got the contourf on plane Z=-10.
My MATLAB version is
MATLAB Version: 8.5.0.197613 (R2015a)
hope the codes work 4 you
clear all
clc
[X,Y,Z] = peaks;
[~,hContour] = contourf(X,Y,Z,20,'edgecolor','none');
hContour.ContourZLevel = -10; % set the contour's Z position
view(44,30)
colormap(jet)

Multiple Data Sets on the Same Scatter

I know this question may sound a bit easy, but I couldn't find what I wanted in the documentation. Basically, I would like to know if a function exists in matlab which allows me to plot data sets y1, y2, ..., yn against the same x-axis in the same scatter diagram.
Any suggestions?
Thanks
You can just use scatter + hold on. For example,
x = rand(1,10);
y1 = rand(1,10);
y2 = rand(1,10);
y3 = rand(1,10);
figure; grid on;
hold on;
scatter(x, y1);
scatter(x, y2);
scatter(x, y3);
Gives:

Combine the legends of shaded error and solid line mean

I am using this FEX entry to plot the horizontal shaded error bars for a variable plotted on the X-axis. This variable is plotted in different regions/zones and, therefore, there are 3 shaded error bars for 3 zones. I would like to combine the legends of the error bars (shaded region) as well as the mean (solid line) of any zone into a single legend represented by a solid line (or solid line inside a patch) of the same color as the zone.
THE WAY MY CODE WORKS FOR PLOTTING: A synthetic example of the way I am plotting is shown below
fh = figure();
axesh = axes('Parent', fh);
nZones = 4;
nPts = 10;
X = nan*ones(nPts, nZones);
Y = nan*ones(nPts, nZones);
XError = nan*ones(10, 4);
clr = {'r', 'b', 'g', 'm', 'y', 'c'};
for iZone = 1:nZones
X(:, iZone) = randi(10, nPts, 1);
Y(:, iZone) = randi(10, nPts, 1);
XError(:, iZone) = rand(nPts, 1);
% Append Legend Entries/Tags
if iZone == 1
TagAx = {['Zone # ', num2str(iZone)]};
else
TagAx = [TagAx, {['Zone # ', num2str(iZone)]}];
end
hold(axesh, 'on')
[hLine, hPatch] = boundedline(X(:, iZone), Y(:, iZone), XError(:, iZone),...
strcat('-', clr{iZone}), axesh, 'transparency', 0.15,...
'orientation', 'horiz');
legend(TagAx);
xlabel(axesh, 'X', 'Fontweight', 'Bold');
ylabel(axesh, 'Y', 'Fontweight', 'Bold');
title(axesh, 'Error bars in X', 'Fontweight', 'Bold');
end
THE WAY LEGENDS ARE SHOWING-UP CURRENTLY:
WHAT I HAVE TRIED:
As someone suggested in the comment section of that file's FEX page to add the following code after line 314 in boundedline code.
set(get(get(hp(iln),'Annotation'),'LegendInformation'),'IconDisplayStyle','off');
However, on doing that I get this error:
The name 'Annotation' is not an accessible property for an instance of
class 'root'.
EDIT: The first two answers suggested accessing the legend handles of the patch and line which are returned as output the by the function boundedline. I tried that but the problem is still not solved as the legend entries are still not consistent with the zones.
There is a direct general way to control legends. You can simply choose not to display line entries by fetching their 'Annotation' property and setting the 'IconDisplayStyle' to 'off'.
Also, in order to use chadsgilbert's solution, you need to collect each single patch handle from every loop iteration. I refactored your code a bit, such that it would work with both solutions.
% Refactoring you example (can be fully vectorized)
figure
axesh = axes('next','add'); % equivalent to hold on
nZones = 4;
nPts = 10;
clr = {'r', 'b', 'g', 'm', 'y', 'c'};
X = randi(10, nPts, nZones);
Y = randi(10, nPts, nZones);
XError = rand(nPts, nZones);
% Preallocate handles
hl = zeros(nZones,1);
hp = zeros(nZones,1);
% LOOP
for ii = 1:nZones
[hl(ii), hp(ii)] = boundedline(X(:, ii), Y(:, ii), XError(:, ii),...
['-', clr{ii}], 'transparency', 0.15, 'orientation', 'horiz');
end
The simplest method as shown by chadsgilbert:
% Create legend entries as a nZones x 8 char array of the format 'Zone #01',...
TagAx = reshape(sprintf('Zone #%02d',1:nZones),8,nZones)'
legend(hp,TagAx)
... or for general more complex manipulations, set the legend display properties of the graphic objects:
% Do not display lines in the legend
hAnn = cell2mat(get(hl,'Annotation'));
hLegEn = cell2mat(get(hAnn,'LegendInformation'));
set(hLegEn,'IconDisplayStyle','off')
% Add legend
legend(TagAx);
There is not an easy way to do this, You have to go into the legend and shift things up:
The following makes a sample plot where the mean lines are not superimposed on the bound patches.
N = 10;
x = 1:N;
y = sin(x);
b = ones([N,2,1]);
[hl, hp] = boundedline(x, y, b);
lh = legend('hi','');
We get a structure g associated with the legend handle lh. If you look at the types of the children, you'll see that g2 is the mean line and g4 is the patch. So I got the vertices of the patch and used it to shift the mean line up.
g = get(lh);
g2 = get(g.Children(2));
g4 = get(g.Children(4));
v = g4.Vertices;
vx = unique(v(:,1));
vy = diff(unique(v(:,2)))/2;
vy = [vy vy] + min(v(:,2));
set(g.Children(2), 'XData', vx);
set(g.Children(2), 'YData', vy);
Not a simple answer, and definitely requires you do a crazy amount of formatting for a plot with more than one mean line/patch pair, especially since it will leave gaps in your legend where the last zone's mean line was.
As per your comment, if you just want the shaded error bars to mark the legend then it's pretty easy:
x = 0:0.1:10;
N = length(x);
y1 = sin(x);
y2 = cos(x);
y3 = cos(2*x);
b1 = ones([N,2,1]);
b2 = 0.5*ones([N,2,1]);
b3 = 0.1*ones([N,2,1]);
hold on
[hl, hp] = boundedline(x, y1, b1, 'r', x, y2, b2, 'b', x, y3, b3,'c')
lh = legend('one','two','three');
That's a nice FEX entry. You can associate a specific set of handles with a legend. And luckily, boundedline already returns the handles to the lines separately from the handles to the patches.
>> [hl,hp] = boundedline([1 2 3],[4 5 6], [1 2 1],'b',[1 2 3],-[4 5 6],[2 1 1],'r');
>> %legend(hl,{'blue line' 'red line'}); % If you want narrow lines in the legend
>> legend(hp,{'blue patch' 'red patch'}); % If you want patches in the legend.