Setting a title for a legend

1 522 vues (au cours des 30 derniers jours)
Eyal Ben-Hur
Eyal Ben-Hur le 13 Fév 2017
Commenté : Eyal Ben-Hur le 18 Sep 2018
The following code is the most minimal example that I could find. My true case is much more complicated:
x = 1:0.1:10;
y = sin(x);
subplot 211
plot(x,y)
[leg,att] = legend('show');
title(leg,'my title')
leg.Title.Visible = 'on';
subplot 212
plot(x,y)
leg = legend('show');
title(leg,'my title')
This results in:
As you can clearly see, something is wrong with the upper legend title. Somehow, asking for the att output of the legend interfere with its' title. First, for some reason, it makes it invisible, but that is already solved in the code above.
The main problem is with its position - it doesn't seem to have such a property, so once set I can't move it.
I can think of some similar hacks by myself (like using text with the position of the legend), but my situation is very complicated, and I already configure the legend a lot and have several axes in every figure. Thus, I prefer a simple and working solution that rely on the original functionality of the legend title.
I use Matlab 2016a.

Réponse acceptée

Eyal Ben-Hur
Eyal Ben-Hur le 23 Juin 2017
I found the answer after some research of the undocumented properties of the legend. The most direct answer to the problem in the question is to set the position of the legend title back to its place. This can be done with the property Position, that is part of the hidden property NodeChildren, of the legend title.
The legend box is effectively just a small axes object, so the position units are normalized relative to the axes box. If we look at it just after the creation of the legend we get:
[hleg,att] = legend('show');
title(hleg,'my title')
hleg.Title.NodeChildren.Position
ans =
0 0 0
Which is where we see the legend in the question. Now we can move it back to its correct place with:
hleg.Title.NodeChildren.Position = [0.5 1.5 0];
Here I placed it at the center of the horizontal axis (x = 0.5), above the vertical axis (y = 1.5), and the depth axis is irrelevant so it's zero (z = 0):
If you are not bothered by the option that the legend will interfere with the data, (e.g. your legend is outside the data axes) then you can stop here. If not, continue to read...
We could set the background color to white:
hleg.Title.NodeChildren.BackgroundColor = 'w';
But it doesn't look good because the background is only for the text box, and is not aligned with the legend box. So I prefer to enlarge the legend box to include the title and move the legend attributes down to make some space. It takes more effort (because we need to calculate the right position for all elements), but it can be done in the following way:
x = 1:0.1:10;
plot(x,sin(x),x,cos(x))
[hleg,icons,plots] = legend('show');
title(hleg,'my title')
hleg.Title.Visible = 'on';
% the addition in height needed for the title:
title_hight = hleg.Position(4)/numel(plots);
hleg.Position([2 4]) = [hleg.Position(2)-title_hight hleg.Position(4)+title_hight];
% calculate new position for the elements in the legeng:
new_pos = fliplr(0.5/(numel(plots)+1):1/(numel(plots)+1):1);
hleg.Title.NodeChildren.Position = [0.5 new_pos(1) 0];
% set the text to the right position:
leg_txt = findobj(icons,'Type','Text');
txt_pos = cell2mat({leg_txt.Position}.');
txt_pos(:,2) = new_pos(2:end);
set(leg_txt,{'Position'},mat2cell(txt_pos,[1 1],3));
% set the icons to the right position:
leg_att = findobj(icons,'Type','Line');
set(leg_att,{'YData'},mat2cell(repmat(repelem(new_pos(2:end).',...
numel(plots)),1,2),ones(numel(plots)*2,1),2))
The code above is also generalized for more the one data series, although I guess that there are cases where this will fail to give the right solution and will need some fine tunings.

Plus de réponses (2)

Kelly Kearney
Kelly Kearney le 14 Fév 2017
As a workaround, legendflex.m should handle this fine. With this option, you can include the second output from the legend call.
I don't currently include the title handle as output (I should correct that), but you can grab it via findall to format as desired:
x = 1:0.1:10;
y = sin(x);
subplot 211
plot(x,y)
[leg,att] = legendflex(gca, {'data 1'}, 'title', 'my title');
set(findall(leg, 'string', 'my title'), 'fontweight', 'bold');
leg.Title.Visible = 'on';
subplot 212
plot(x,y)
leg = legendflex(gca, {'data 2'}, 'title', 'my title');
set(findall(leg, 'string', 'my title'), 'fontweight', 'bold');
  1 commentaire
Eyal Ben-Hur
Eyal Ben-Hur le 15 Fév 2017
That's nice, but definitely a too complicated workaround for a simple result.

Connectez-vous pour commenter.


KAE
KAE le 17 Sep 2018
Modifié(e) : KAE le 17 Sep 2018
If you limit yourself to one output from the legend command, then the title is positioned correctly...as you noted in your original question.
x = 1:0.1:10;
y = sin(x);
subplot 211
plot(x,y)
leg = legend('show'); % Just one output here
title(leg,'my title')
subplot 212
plot(x,y)
leg = legend('show');
title(leg,'my title')
If you need the other outputs from legend, then the documentation offers a workaround using legend properties (see "Returning multiple outputs is not recommended").
  3 commentaires
KAE
KAE le 18 Sep 2018
Modifié(e) : KAE le 18 Sep 2018
Perhaps the very useful site 'Undocumented Matlab' will have some tips, starting with the blog entry here.
Eyal Ben-Hur
Eyal Ben-Hur le 18 Sep 2018
You can see in my answer above that I have already used the undocumented features to solve this problem.

Connectez-vous pour commenter.

Produits

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by