advanced customization of legend markers in matlab

2019-02-10 18:36发布

问题:

It is relatively simple to add basic modifications to markers in matlab legends. The legend produced by the following code snippet ():

hold on h = plot(inf,inf,'ob',inf,inf,'r+'); legend(h,'Data1','Data2');

Can be easily modified to: using the following code:

[~,~,icons,~] = legend(h,'Data1','Data2'); set(icons(1),'LineStyle','-') set(icons(2),'LineStyle','-')

However things become fairly complicated if I want to correctly legend objects such as (circle is not in the middle of a line) or (several colors for one line with '+' markers on it). I have not found any property or workaround that allows to modify the position of the markers in the legend box, or add several markers in one legend group.

Does anyone know of a document that contains advanced information for legend customization? Or how to better use the numerous properties of graphic objects provided by matlab to achieve what is described above?

回答1:

In the MatLab version up to R2014a the legend box is actually an axes so it is relatively easy to modify its content through its handle.

From version R2014b the legend is a graphics object and seems there is no way to access to the axes handle (ref. to this post on undocumentedmatlab).

Up to R2014a

Given the legend for two lines in a plot:

h = plot((0:.1:2*pi),sin((0:.1:2*pi)),'ob',(0:.1:2*pi),cos((0:.1:2*pi)),'r+');
[a,b,icons,c] = legend(h,'Data1','Data2');
  • a is the handle of the legend axex
  • b is an array of handels:
    • b(1): handle of the first string
    • b(2): handle of the second string
    • b(3): handle of the first line
    • b(4): handle of the marker of the first line
    • b(5): handle of the second line
    • b(6): handle of the marker of the second line

if you want to move the marker on the first line to, for example, the end of the line, you can:

  • get the XData of the line (stored in b(3)): it is an (1x2) array
  • set the XData of the marker (stored in b(4)) to the last value of the array obtained on the above step

If you want to add more marker and have the second line made of more segments with different colours, you can:

  • get the XData and YData of the line (stored in b(5))
  • generate the x coord by splitting the XData array
  • plot the the segments in a for loop using the YData value as y coord

This approach has been implemented in the following code in which, the legend box has been also enlarged to make in more "readable".

The comments in the code should explain the different steps.

% Plot something
h = plot((0:.1:2*pi),sin((0:.1:2*pi)),'ob',(0:.1:2*pi),cos((0:.1:2*pi)),'r+');
% Add trhe legend
[a,b,icons,c] = legend(h,'Data1','Data2');
%
% a ==> handle of the legend axes
% b(1) ==> handle of the first string
% b(2) ==> handle of the second string
% b(3) ==> handle of the first line
% b(4) ==> handle of the marker of the first line
% b(5) ==> handle of the second line
% b(6) ==> handle of the marker of the second line
%
% Get positin and size of the legend box
ax_p=get(a,'position')
% Enlarge the legend box
set(a,'position',[ax_p(1)-.2 ax_p(2) ax_p(3)+.2 ax_p(4)])
% Set the linestyle of the first element on the legend
set(b(3),'linestyle','-')
% Get the XData of the first line
xl_1=get(b(3),'xdata')
% Move the marker of the first line to the end of the line
set(b(4),'xdata',xl_1(2))
% Get the position of the first string
xs_1=get(b(1),'position')
% Move the first string
set(b(1),'position',[xs_1(1)+.2 xs_1(2) xs_1(3)])
% Get the position of the second string
xs_2=get(b(2),'position')
% Move the second string
set(b(2),'position',[xs_2(1)+.2 xs_2(2) xs_2(3)])
% Split the second line in multi-color segment and add more marker on the
% second line
%
% Define the number of segments
n=4;
% Get the XData of the first line
xl_2=get(b(5),'xdata')
% Get the YData of the first line
yl_2=get(b(5),'ydata')
% Define the segments
len=linspace(xl_2(1),xl_2(2),n+1);
% Plot the segments of the second line in different colours
for i=1:n
   plot(a,[len(i) len(i+1)],[yl_2(1) yl_2(2)], ...
      'marker',get(b(6),'marker'),'markeredgecolor', ...
      get(b(6),'markeredgecolor'),'markerfacecolor',get(b(6),'markerfacecolor'), ...
      'color',rand(1,3),'linewidth',2)
end

This is the result:

From R2014b

Since it seems not possible to access to the legend axex, a solution could be(as suggested in the above mentoined post to add an axes and superimpose it to the legend.

You could firt create the legend:

h = plot((0:.1:2*pi),sin((0:.1:2*pi)),'o-',(0:.1:2*pi),cos((0:.1:2*pi)),'r+-');
[a,b,icons,c] = legend(h,'Data1','Data2');
  • a is an object of matlab.graphics.illustration.Legend (try class(a))
  • b is an array of matlab.graphics.primitive.Data objects (try class(b))

in a similar way to the older versions, the b refers to:

  • b(1): first string
  • b(2): second string
  • b(3): first line
  • b(4): marker of the first line
  • b(5): second line
  • b(6): marker of the second line

You can get the position and size of the legend through the legend object a.

You can then apply the same approach described above in order to plot the "updated" legend.

This approach has been implemented in the following code (the comments should explains the different steps).

% Plot something
h = plot((0:.1:2*pi),sin((0:.1:2*pi)),'o-',(0:.1:2*pi),cos((0:.1:2*pi)),'r+-');
% Add the legend
[a,b,icons,c] = legend(h,'Data1','Data2');
% Add an axes to the figure
ax=axes;
% Enlarge the legend, then set the axes position and size equal to the
% legend box
%Get the legend's position and size
ax_p=a.Position;
a.Position=[ax_p(1)-.2 ax_p(2) ax_p(3)+.2 ax_p(4)];
ax.Position=a.Position;
ax.Units='normalized';
ax.Box='on';
% Plot the firt line in the axes
plot(ax,b(3).XData,b(3).YData,'color',b(3).Color);
hold on
% Add the marker of the first line at the end of the line
plot(ax,b(3).XData(end),b(3).YData(end), ...
             'marker',b(4).Marker, ...
             'markeredgecolor',b(4).Color, ...
             'markerfacecolor',b(3).MarkerFaceColor);
% Get second line XData and YData
x=b(5).XData;
y=b(5).YData;
% Define the number of line sections
n=5;
% Update the XData and YData by defning intermediate values
len=linspace(x(1),x(2),n);
% Plot the set of line with different colours
for i=1:n-1
   plot(ax,[len(i) len(i+1)],[y(2) y(2)], ...
      'marker',b(6).Marker,'markeredgecolor',b(6).Color, ...
      'markerfacecolor',b(6).MarkerFaceColor, ...
      'color',rand(1,3),'linewidth',1.5);
end
% Get the legend texts position
pt1=b(1).Position;
pt2=b(2).Position;
% Add the legend text
text(pt1(1)+.1,pt1(2),a.String{1});
text(pt2(1)+.1,pt2(2),a.String{2});
% Remove the axes ticks
ax.XTick=[];
ax.YTick=[];
% Set the axes limits
ax.XLim=[0 1];
ax.YLim=[0 1];

Hope this helps.

Qapla'