How to put the text labels (keys) to the left of the lines in the legend?
Asked Answered
D

3

6

I searched in many places and didn't find a way to do what I would like to achieve using MATLAB which is this: putting the keys (text labels) to the left of the color lines in the legend in MATLAB.

enter image description here

I would like to achieve this result that is the default result using gnuplot for instance (cf link above) and that is apparently doable using MATLAB's cousin Octave (see Octave's legend) that has an option to do legend("left") that "place[s] label text to the left of the keys."

Is it possible to do so using MATLAB?

Duval answered 17/5, 2016 at 21:13 Comment(2)
Interesting question. MATLAB's legend indeed does not cite an option to do this. I guess someone with HG2 knowledge would have to hack their way through MATLAB's underbelly.Attempt
I was thinking about something along these lines and actually even tried legend('left') directly, but to no avail. I would like to add that I use R2013b and that a solution working with HG1 would be nice too, even though I could request a newer version to my institute probably.Duval
T
5

Here's a solution that works on HG1 and HG2 alike.

The idea is somewhat similar to that in @Suever's answer - we want to flip around all text, lines and markers inside the legend, however there are some notable differences:

  1. There's no need to search for line and text handles using findall - they are available as the second output of legend function.
  2. There's no need to guess what the reasonable position should be. We just need to flip all controls symmetrically. With lines it's easy as you have XData with both start and end points. With text it's a bit more tricky since you need to control Position, i.e. left edge, but you need to know text width to calculate symmetrical transformation. Extent property gives us what we're missing.

Code:

hFig = figure();
axh = axes('Parent', hFig);
plot(axh, randn(100,2), '-*');
[~, hObjs] = legend(axh, {'first','second-some-long-text'});

for i = 1:length(hObjs)
    hdl = hObjs(i);
    if strcmp(get(hdl, 'Type'), 'text')
        pos = get(hdl, 'Position');
        extent = get(hdl, 'Extent');    % we need text x-position and width
        pos(1) = 1-extent(1)-extent(3); % symmetrical relative to center
        set(hdl, 'Position', pos);
    else     % 'line'
        xData = get(hdl, 'XData');
        if length(xData) == 2 % line
            xDataNew = [1-xData(2), 1-xData(1)];
        else % length(xData)==1, i.e. marker
            xDataNew = 1-xData;
        end
        set(hdl, 'XData', xDataNew);
    end
end

enter image description here

Toxic answered 17/5, 2016 at 22:16 Comment(5)
Just set the HorizontalAlignment of the text to right so they are right justified rather than dealing with Extent.Praxis
It worked also very well for me indeed, actually even putting the text a little closer to the lines than @Suever's solution. I cannot test for HG2 so I will wait a little bit, but unless somebody has an objection this is the best answer.Duval
@Suever, Well, the labels actually appear right-justified simply by construction. If the label was short and close to centre, after symmetrical flip it will still be close to centre. Is it not for you?Toxic
@viiv, yes, all the gaps should be the same as before due to symmetry of the transformation. I also tested it on Matlab 2015b HG2, so it definitely works : )Toxic
Both answers work very well, and I'll accept this one since the gaps between the lines and text is smaller in my case.Duval
P
7

Updated slightly using the second output of legend mentioned by @nirvana-msu.

We can adjust the positions of the text and plot object in the legend by retrieving their current position and altering it. The position units of all items in the legend are normalized data units between 0 and 1.

Something like the following should work.

%// Create some random data and display
figure;
p = plot(rand(2,3));
[L, handles] = legend({'one', 'two', 'three'});

%// Get the text handles
texts = findall(handles, 'type', 'text');

%// Get the line handles
lines = findall(handles, 'type', 'line');

%// Get the XData of all the lines
poslines = get(lines, 'XData');

%// Subtract 1 from all the positions and take the absolute value
%// this will shift them to the other side as the xlims are [0 1]
set(lines, {'XData'}, cellfun(@(x)abs(x - 1), poslines, 'uni', 0))

%// Get the position of all of the text labels
postext = get(texts, 'Position');

%// Figure out where the lines ended up
xd = get(lines, 'xdata');
xd = cat(2, xd{:});

%// Have the labels be next to the line (with 0.05 padding)
positions = cellfun(@(x)[min(xd) - 0.05, x(2:3)], postext, 'uni', 0);
set(texts, {'Position'}, positions, ...
           'HorizontalAlignment', 'right');

R2014a

enter image description here

R2015b

enter image description here

Praxis answered 17/5, 2016 at 21:47 Comment(2)
It worked very well in my example where I actually manipulate the figure a little more after to have a gnuplot-like look by trimming margins and such, and when I print it actually I didn't even have to add the modifications you suggest about the reformatting of the legend. Should I accept this as an answer or wait for an answer for HG2 on R2014b+ ?Duval
I tried and it definitely works too, and there was indeed no need for legend reformattingDuval
T
5

Here's a solution that works on HG1 and HG2 alike.

The idea is somewhat similar to that in @Suever's answer - we want to flip around all text, lines and markers inside the legend, however there are some notable differences:

  1. There's no need to search for line and text handles using findall - they are available as the second output of legend function.
  2. There's no need to guess what the reasonable position should be. We just need to flip all controls symmetrically. With lines it's easy as you have XData with both start and end points. With text it's a bit more tricky since you need to control Position, i.e. left edge, but you need to know text width to calculate symmetrical transformation. Extent property gives us what we're missing.

Code:

hFig = figure();
axh = axes('Parent', hFig);
plot(axh, randn(100,2), '-*');
[~, hObjs] = legend(axh, {'first','second-some-long-text'});

for i = 1:length(hObjs)
    hdl = hObjs(i);
    if strcmp(get(hdl, 'Type'), 'text')
        pos = get(hdl, 'Position');
        extent = get(hdl, 'Extent');    % we need text x-position and width
        pos(1) = 1-extent(1)-extent(3); % symmetrical relative to center
        set(hdl, 'Position', pos);
    else     % 'line'
        xData = get(hdl, 'XData');
        if length(xData) == 2 % line
            xDataNew = [1-xData(2), 1-xData(1)];
        else % length(xData)==1, i.e. marker
            xDataNew = 1-xData;
        end
        set(hdl, 'XData', xDataNew);
    end
end

enter image description here

Toxic answered 17/5, 2016 at 22:16 Comment(5)
Just set the HorizontalAlignment of the text to right so they are right justified rather than dealing with Extent.Praxis
It worked also very well for me indeed, actually even putting the text a little closer to the lines than @Suever's solution. I cannot test for HG2 so I will wait a little bit, but unless somebody has an objection this is the best answer.Duval
@Suever, Well, the labels actually appear right-justified simply by construction. If the label was short and close to centre, after symmetrical flip it will still be close to centre. Is it not for you?Toxic
@viiv, yes, all the gaps should be the same as before due to symmetry of the transformation. I also tested it on Matlab 2015b HG2, so it definitely works : )Toxic
Both answers work very well, and I'll accept this one since the gaps between the lines and text is smaller in my case.Duval
A
4

I've decided to take a slightly different approach for the HG2 case. Although admittedly somewhat silly, it could actually be useful if the figure ends up being exported.

What we do is pretty much rotate every conceivable thing by 180°:

function q37286345
%// Define some functions:
f = {'sin(x)','cos(x)','-2E-3.*x.^3'};
figure(); hold on;
for ind1 = 1:numel(f)
  ezplot(f{ind1}, [-10 10]); 
end
title(''); %// Just because I find the default annoying :)
%// Rotate stuff:
[L,H] = legend(f{:},'Location','SouthWest');
[H(1:3).Rotation] = deal(180);
[H(1:3).HorizontalAlignment] = deal('right');
set(gca,'XTickLabelRotation',180,'YTickLabelRotation',180,...
        'XAxisLocation','top','YAxisLocation','right');

The result looks approximately like this:

The rotated image

There are several easily-remedied side effects:

  • The legend entry order will be an opposite of the plotting order (unless the Children of Legend are reordered.
  • The axes' labels will also require rotation (or elimination...).

Side note:

We can explore the Legend metaclass ('matlab.graphics.illustration.Legend') to learn more about HG2:

%% // Exploring.....
mc = metaclass(L);
pl = {mc.PropertyList.Name}.'; %'
ml = {mc.MethodList.Name}.'; %'
s = struct(L);
Auburn answered 17/5, 2016 at 23:55 Comment(1)
Haha I like this. Very clever!Praxis

© 2022 - 2024 — McMap. All rights reserved.