1

I want to add an entry manually to a MATLAB legend. This legend can be pre-existent and contain other graphed elements' entries, but not necessarily.

I make a scatter plot, but instead of using e.g. scatter(x,y), I plot it using

for n = 1:numel(x)
    text(x(n),y(n),num2str(n), ...
    'HorizontalAlignment','center','color',[1 0 0])
end

This results in a scatter plot of numbers one through the number of elements in x (and y, because they are of the same size). I want to add a legend entry for these numbers.

I tried to add or edit the legend with

[h,icons,plots,s] = legend(___)

as described on the legend documentation page. I can't figure out how I can add a legend entry, without having to plot something (such as an actual scatter plot or regular plot). I want the usual line or marker symbol in the legend to be a number or character such as 'n', indicating the numbers in the graph. Is this possible and how would one achieve this?

Erik
  • 4,305
  • 3
  • 36
  • 54
  • Could you fake it with an [`annotation`](http://uk.mathworks.com/help/matlab/ref/annotation.html)? – Steve Nov 09 '15 at 23:46
  • @Steve - Yes, but then it wouldn't work if the graph already had a legend for any other graphed elements. In that case I would want to add my custom entry to that legend. – Erik Nov 09 '15 at 23:52
  • 1
    Perhaps an option could be to create hidden (white) points (using code similar to [this answer](http://stackoverflow.com/a/33475174/2433501)) then add the number to the legend text instead, for example `1: foo`, `2: bar`. It might also be possible to add a placeholder to the legend in this way, then fake it by adding the text as an annotation on top (although the legend might hide it). – zelanix Nov 10 '15 at 00:25
  • @zelanix - thanks for the link and suggestions. I thought of the following: I could make the default legend line or marker invisible (can be done with `icons`, the `legend` output in the question). Then I could alter the legend text position (also in `icons`). The text string then could become 'n␣␣␣(as spaces as needed to look OK)␣␣␣legend entry text'. This would then be all black text, which is OK for black numbers. However, if I wanted to add another scatter of numbers with a different colour, the 'n' couldn't be in that colour. – Erik Nov 10 '15 at 00:33
  • Aren't MATLAB's legends just axes, only wrapped in some of the more modern graphics engine wrappers and quite hard-coded and inaccessible, compared to regular axes? I could create my own function that instead of adding the normal legend, it adds axes that look exactly like the real legend, incl. any current entries in the legend. This would require quite an amount of programming, however, for which I don't have the time. – Erik Nov 10 '15 at 00:50
  • On a side note: the legend handle is quite strange. E.g. its children: it has an empty graphics placeholder. However, the `icons` contains handles that ultimately all have the very legend as their parent... Technically it's impossible for a child to have a parent, but the parent not to have a child! – Erik Nov 10 '15 at 00:54
  • I think that it used to be the case that the legend was just an axes with children, but it is no longer possible to add new child elements to the legend. Interestingly the elements of `icons` does show the parent as the legend, but these must be special in some way. I have posted a possible solution. Your idea of shifting the text to the left and padding it would work too, although with the caveats that you mentioned. – zelanix Nov 10 '15 at 01:09

1 Answers1

1

EDIT by Erik

My answer goes below zelanix's answer, because mine is based on it.


Original answer

A fairly workable solution may be as follows:

x = rand(10, 1);
y = rand(10, 1);

figure;

text(x,y,num2str(transpose(1:numel(x))),'HorizontalAlignment','center')

% Create dummy legend entries, with white symbols.
hold on;
plot(0, 0, 'o', 'color', [1 1 1], 'visible', 'off');
plot(0, 0, 'o', 'color', [1 1 1], 'visible', 'off');
hold off;

% Create legend with placeholder entries.
[h_leg, icons] = legend('foo', 'bar');

% Create new (invisible) axes on top of the legend so that we can draw
% text on top.
ax2 = axes('position', get(h_leg, 'position'));
set(ax2, 'Color', 'none', 'Box', 'off')
set(ax2, 'xtick', [], 'ytick', []);

% Draw the numbers on the legend, positioned as per the original markers.
text(get(icons(4), 'XData'), get(icons(4), 'YData'), '1', 'HorizontalAlignment', 'center')
text(get(icons(6), 'XData'), get(icons(6), 'YData'), '2', 'HorizontalAlignment', 'center')

axes(ax1);

Output:

enter image description here

The trick to this is that the new axes are created in exactly the same place as the legend, and the coordinates of the elements of the icons are in normalised coordinates which can now be used inside the new axes directly. Of course you are now free to use whatever font size / colour / whatever you need.

The disadvantage is that this should only be called after your legend has been populated and positioned. Moving the legend, or adding entries will not update the custom markers.


Erik's answer

Based on zelanix's answer above. It is a work-in-progress answer, I am trying to make a quite flexible function of this. Currently, it's just a script that you'd need to adapt to your situation.

% plot some lines and some text numbers
f = figure;
plot([0 1],[0 1],[0 1],[1 0])
x = rand(25,1);
y = rand(25,1);
for n = 1:numel(x)
    text(x(n),y(n),num2str(n), ...
    'HorizontalAlignment','center','color',[1 0 0])
end
hold on
% scatter(x,y) % used to test the number positions
scatter(x,y,'Visible','off') % moves the legend location to best position

% create the dummy legend using some dummy plots
plot(0,0,'o','Visible','off')
[l,i] = legend('some line','some other line','some numbers','location','best');
l.Visible = 'off';

% create empty axes to mimick legend
oa = gca; % the original current axes handle
a = axes;
axis manual
a.Box = 'on';
a.XTick = [];
a.YTick = [];

% copy the legend's properties and contents to the new axes
a.Units = l.Units; % just in case
a.Position = l.Position;
i = copyobj(i,a);

% replace the marker with a red 'n'
s = findobj(i,'string','some numbers');
% m = findobj(i(i~=s),'-property','YData','marker','o');
m = findobj(i(i~=s),'-property','YData');
sy = s.Position(2);
if numel(m)>1
    dy = abs(m(1).YData - sy);
    for k = 2:numel(m)
        h = m(k);
        dy2 = abs(h.YData - sy);
        if dy2<dy
            kbest = k;
            dy = dy2;
        end
    end
    m = m(kbest);
end
m.Visible = 'off';
mx = m.XData;
text(mx,sy,'n','HorizontalAlignment','center','color',[1 0 0])

% reset current axes to main axes
f.CurrentAxes = oa;

The result:

enter image description here

Community
  • 1
  • 1
zelanix
  • 3,326
  • 1
  • 25
  • 35
  • Very nice. I'd add a line to make the original legend invisible, just in case. Also, it might be possible to copy all elements of `icons` to the new axes using `copyobj`: this would result in a very nice new axes overlay with all the current legend entries. The most trouble would be to adjust the new axes position and size to fit all the existing and new entries. The easiest way to do this would be to use the normal `legend` function to do all the work for you, then use your solution to create new axes based on it and copy over all the object you want (and leave out or edit what isn't needed). – Erik Nov 10 '15 at 01:15
  • @Erik Interesting idea, so you would essentially recreate the whole legend in the new axes. This would also give some nice features such as being able to drag it around with the hand tool etc. As long as you copy everything *after* creating the original legend (with dummy entries) then you shouldn't have any positioning problems as the axes uses normalised coordinates. I think it depends how general you need to be in your application. – zelanix Nov 10 '15 at 01:22
  • I'm looking into it right now and I'm trying to create a script that will do just this. I will post it here if it works and adds to your solution. Thanks for the help! – Erik Nov 10 '15 at 01:31
  • No problem! Good luck. Please do post back if it works - I'd be very interested to see it. – zelanix Nov 10 '15 at 01:33
  • I've edited your answer to include my own based on yours. – Erik Nov 11 '15 at 13:18