5

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:

enter image description here

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.

desertnaut
  • 57,590
  • 26
  • 140
  • 166

3 Answers3

4

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);

enter image description here

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');

enter image description here

kitchenette
  • 1,625
  • 11
  • 12
  • Thanks. Your example works fine, but when I am doing it for mine it gives me an error `Index exceeds matrix dimensions` at the line `g4 = get(g.Children(4));`. I am plotting one curve at each iteration step and superimposing them with the plots from previous iterations. –  Jul 12 '13 at 16:04
  • That's why it's not an easy answer because you have to look into each of the children yourself and determine based on the properties if it's a Text or a Line or Patch and then determine how you're going to shift each of them. You can't just plug and play with this solution unfortunately.... – kitchenette Jul 12 '13 at 16:22
  • Apparently, the # of children for the structure `g` in my case are `2`. I am not sure why is that but it could be due to the fact that I am using the horizontal error bars instead of vertical error bars. –  Jul 12 '13 at 16:22
  • When you make your legend, the inputs to your legend command determines the number of children in structure g – kitchenette Jul 12 '13 at 16:24
  • It worked for the 1st iteration where I added `''` as an extra entry to the legends and that took care of the 4 children. However, for the 2nd iteration it failed as there are 7 children now which I am not sure how is that.. –  Jul 12 '13 at 16:32
  • Could this be made simpler if I just want the shade or the line (not the both of them as you did) as a legend tag for an individual zone? –  Jul 12 '13 at 16:46
  • I think that's possible. See above. – kitchenette Jul 12 '13 at 20:39
  • It doesn't works if I constantly append my legend with new entries at every loop iteration. For example, if I do `legend(hl, handles.plotTagAx{3})` or `legend(hp, handles.plotTagAx{3})` then the old legends are just replaced by the new ones and they do not concatenate. Here `S.T2Ax(1)` is the axes handle and the legend entries are updated at each iteration as `plotTagAx{3} = [handles.plotTagAx{3}, {['Zone # ', num2str(iZ)]}];` –  Jul 13 '13 at 17:11
  • On placing the legend outside the loop (`legend(handles.hPatch, handles.plotTagAx{3});`) it does not appends the legend entries, but rather ignores the entries other than the 1st entry. –  Jul 15 '13 at 18:43
4

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);
Community
  • 1
  • 1
Oleg
  • 10,406
  • 3
  • 29
  • 57
  • Thanks Oleg for your answer. I tried both the methods and they work perfectly. However, do you think we could display the legends with the progression of loop iteration? As in, as each iteration/zone has run, the legend for that zone shows up simultaneously. I tried putting the lines of code within the loop but it throws up an error. –  Jul 18 '13 at 21:41
  • 1
    @Pupil Inside the loop, after boundedline(...), place: `set(get(get(hl(ii),'Annotation'),'LegendInformation'),'IconDisplayStyle','off')‌​; legend(reshape(sprintf('Zone #%02d',1:ii),8,ii)'); pause(0.5)`. – Oleg Jul 18 '13 at 23:17
1

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.
chadsgilbert
  • 390
  • 1
  • 13
  • Thanks, Chad. As I am using `hold on` to add the plots, I am not sure how I can concatenate my legends in that case if I pass a plot handle such as `hl` or `hp` to the legend. Right now I am concatenating my legend tags as `plotTagAx{3} = [handles.plotTagAx{3}, {['Zone # ', num2str(iZ)]}];` and then using `legend(S.T2Ax(1), handles.plotTagAx{3})` to pass them to the axes. However, if I do `legend(hl, handles.plotTagAx{3})` or `legend(hp, handles.plotTagAx{3})` then the old legends are just replaced by the new ones and they do not concatenate. Here `S.T2Ax(1)` is the axes handle. –  Jul 12 '13 at 15:27
  • Hi @Pupil, I'm having a bit of trouble following. If I understand correctly, you have something like: ` hold on; for ii = 1:N boundedplot(...) legend('yadda yadda'); end ` If you aggregate your plot handles and them make one call to the legend, will that work? E.g. ` hold on; for ii = 1:N [hl(end+1) hp(end+1)] = boundedplot(...) end legend(hl,'yadda yadda', 'etc'); ` – chadsgilbert Jul 12 '13 at 15:40
  • Right now I pass the tags names of my legend to the axes handle `S.T2Ax(1)` rather than to the plot handle `hl` or `hp` because passing them to the axes handle allows me to concatenate the legend entries on the axes. I tried passing the plot handles (say `hp`) to the legend, however, it does removes the previous legends when adding new one and, therefore, does not concatenates the legends. –  Jul 12 '13 at 15:44
  • Hm. Can't get the markdown to work in comments. Sorry for the mess! – chadsgilbert Jul 12 '13 at 15:44
  • I tried that as well where let's say for `ii = 2` I will have two entries in handle `hp` as `hp(1)` and `hp(2)` and then doing` `legend(hp, ['tag1', 'tag2'])` does not works. –  Jul 12 '13 at 15:47
  • Oh I see. You're already passing the axes handle into the `legend` command. Yeah, that throws a wrench in things. I'm sure you'll be able to collect the handles and then make a single `legend` at the end, instead of updating it on every iteration of your loop. But I'd have to see all your code in order to help with that. – chadsgilbert Jul 12 '13 at 15:48
  • You'll need to place the legend titles in a cell array (`{}`), instead of concatenating them with `[]`. – chadsgilbert Jul 12 '13 at 15:49
  • `{['a' 'b']}` will give you: `{'ab'}`. But you need {'a' 'b'}. – chadsgilbert Jul 12 '13 at 16:03
  • I am doing this: `['1st', {['second tag is ', num2str(2)]}]`, or in actual code it is `plotTagAx{3} = [handles.plotTagAx{3}, {['Zone # ', num2str(iZ)]}];` –  Jul 12 '13 at 16:09