single legend entry for all lines of plotted array?

Here is what I am trying to do: I have a matrix of spectra (wpsec.spec, ns x nv where ns=number of spectra) and would like to plot as two different types of lines along with a few other lines of information.
> plot(1:nv,wspec.spec(gout,:),'b',1:nv,wspec.spec(~gout,:),':k',1:nv,cent,'g',1:nv,low,'r',1:nv,up,'r');
plots all the spectra identified by logical index "gout" as blue lines ('b') and all other spectra as dotted black lines (':k') along with a green line for "cent" (1 x nv), and red lines for "low" and "up" (both 1 x nv). This works ok to produce the plot. Now I want to add a legend with 4 entries ("outliers","normal","central value" and "thresholds") as
> legend('outliers','normal','central value','thresholds')
Of course this doesn't work -- it will just put the labels on the first 5 blue lines of "wspec.spec(gout,:)" , assuming that nnz(gout)>5. What is needed is to "group" all the blue lines under a single legend entry. But you cannot just:
> p(1) = plot(1:nv,wspec.spec(~gout,:),'b','DisplayName','outliers');
since we get an error "Subscripted assignment dimension mismatch." as it wants to apply 'DisplayName' to all nnz(gout) blue lines. It would be nice if the 'DisplayName' property had this flexibility to automatically expand 'outliers' to apply to all the lines...sigh.
So, what is the best way to accomplish what I want? I know I could plot just the first line of each multiline array, generate the legend, and then plot the remaining lines of each array:
> p(1) = plot(1:nv,wspec.spec(find(gout,1,'first'),:),'b','DisplayName','outliers');
> hold on;
> p(2) = plot(1:nv,wspec.spec(find(~gout,1,'first'),:),':k','DisplayName','normal');
> p(3) = plot(1:nv,cent,'g','DisplayName','Central Value');
> p(4) = plot(1:nv,low,'r','DisplayName','Threshold');
> p(5) = plot(1:nv,up,'r');
> p(6) = plot(1:nv,wspec.spec(~gout,:),':k'); %results in duplicate plotting of first line
> p(7) = plot(1:nv,wspec.spec(gout,:),'b'); %results in duplicate plotting of first line
> hold off;
> legend(p(1:4));
but this seems un-tidy and has other problems with layering lines on top of other lines depending on the order in the plot commands. With all the changes in the graphics engine structure, I am wondering if there isn't already a way to associate legend entries to a "container" of plot lines.

3 Comments

"With all the changes in the graphics engine structure" Are you referring to HG1 vs HG2?
Anyway, the dimension mismatch is due to trying to save multiple handles to one spot in an array. You can either save to a temporary array and save only 1 handle to the array, or use a cell array. I generally use the second option. This doesn't really group all lines together, but at least it works for a legend.
Actually, I was referring to either HG1 or HG2 -- I come from the "olden days" of Matlab before either one...never really caught up, lol.
You are most likely correct that a solution would be to expand out the "DisplayName" to match the dimensions of the number of lines plotted. I just was looking for a way to not add all that reduncency when I am really just trying to get one legend entry for all spectra (lines) in a plot group. Now if there was a way to set "DisplayName" to only the first line plotted in the group and leave the rest unset, then it may be useful. I cannot see a way to specify that other than a separate "plot" call for each line -- and that really is inefficient when each group might have hundreds, or thousands, of lines plotted.
It seems like a common enough need in these days of large data, but I guess there isn't a compact solution without customized code for each plot generated.
I am reposting the example code with some more comments...hopefully that will make it clearer what I want to do.
> % this line just plots the first line of the group defined by "gout"
% logical index. This single line gets a "DisplayName" defined so
% it should generate a legend entry
> p(1) = plot(1:nv,wspec.spec(find(gout,1,'first'),:),'b','DisplayName','outliers');
> hold on;
> % this line plots the first line of the second group defined by "~gout"
% logical index. This single line also gets a "DisplayName" defined
> p(2) = plot(1:nv,wspec.spec(find(~gout,1,'first'),:),':k','DisplayName','normal');
> % the next 2 lines plot the "central value" line and the "lower threshold" --
> % both get a "DisplayName" defined.
> p(3) = plot(1:nv,cent,'g','DisplayName','Central Value');
> p(4) = plot(1:nv,low,'r','DisplayName','Threshold');
> % next line plots the "upper threshold", but it does not get a "DisplayName"
% defined so it should "share" legend entry with the previous threshold
% line
> p(5) = plot(1:nv,up,'r'); %no "DisplayName" so no legend entry
> % next we plot the rest of the second group (~gout index) lines -- being
% lazy as it also re-plots the first line plotted in "p(2)" above.
% This time "DisplayName" is not defined so there should be no legend entries
% for these lines
> p(6) = plot(1:nv,wspec.spec(~gout,:),':k'); %no legend entry
> % lastly, we plot the rest of the first (gout index) group lines (first
> % line from "p(1)" above gets re-plotted), but no "DisplayName" defined
> p(7) = plot(1:nv,wspec.spec(gout,:),'b'); %no legend entry
> hold off;
> legend(p(1:4)); %legend generated from subset of plots. It may be
%enough to just use legend('show'), but I do not
%think it hurts to restrict to the first 4 plots
%where "legend()" looks for entries.
Most of the time I do not bother with setting the "DisplayName" for those lines I want legend entries for -- I just rely on the order of single line plots and just supply legend() the strings I want entries for.
Question: if "DisplayName" is not defined, is that enough to eliminate a legend entry being generated for that line? Or do I also need to set the annotation property for lines (plots) I do not want included?
> set( get( get( p(5), 'Annotation'), 'LegendInformation' ), 'IconDisplayStyle', 'off' );
> set( get( get( p(6), 'Annotation'), 'LegendInformation' ), 'IconDisplayStyle', 'off' );
> set( get( get( p(7), 'Annotation'), 'LegendInformation' ), 'IconDisplayStyle', 'off' );
Thanks

Sign in to comment.

 Accepted Answer

I cannot run your code. Since you have 'DisplayName' defined in every plot call:
p(1) = plot(1:nv,wspec.spec(~gout,:),'b','DisplayName','outliers');
you most likely need only to use the 'show' argument in your legend call:
legend('show')
See if that works.

6 Comments

Yes, that is the example from the documentation. That example was rather simplistic with only a single line plotted with each plot call.
I am looking for a solution where each plot call of a group of data may result in hundreds, or thousands, of lines being drawn and I just want a single legend entry for each group. I was hoping there was a general solution to accomplish this without plotting each line individually.
The only option I can suggest is to plot the first row and return a handle to it, then plot the rest, and refer to the one handle in the legend call.
Example
x = (0:10)*0.1;
y1 = rand(5, 11);
y2 = rand(5, 11)+1;
figure(1)
h1 = plot(x, y1(1,:), '-g');
hold on
plot(x, y1, '-g')
h2 = plot(x, y2(1,:), '-r');
plot(x, y2, '-r')
hold off
legend([h1,h2], 'Green Group', 'Red Group')
This works on HG2 in R2017b. I no longer have access to R2014a, so I cannot test it with HG1.
Another Option
figure(2)
h1 = plot(x, y1, '-g');
hold on
h2 = plot(x, y2, '-r');
hold off
legend([h1(1),h2(1)], 'Green Group', 'Red Group')
I checked on R2012b, so if someone's curious: this also works on HG1 (both methods).
The solution given by Star Strider as his "Another Option -- " works well for my needs. It is simple and requires little additional effort. I am guessing that when we index into the plot handle [i.e., h1(1) or h2(1)] we are accessing the individual Line handles? Cool. Where is this sort of thing documented? Is there a decent HG2 tutorial on-line to learn more? I think I need a little more hand-holding, or explanation, than the Matlab Help provides.
Thanks everyone.
As always, my pleasure.
‘I am guessing that when we index into the plot handle [i.e., h1(1) or h2(1)] we are accessing the individual Line handles?’
You are guessing correctly. In my ‘Another Option’ code, you will see that ‘h1’ and ‘h2’ are handles to several (in my code, 5) line objects. Using them without the subscript in the legend call produces the result you don’t want. Choosing only one produces the result you want.
The documentation for the line handles is in the legend section on Specify Charts to Include in Legend (link).
With respect to HG2, one source is Graphics Changes in R2014b (link). There are more discussions on the Graphics (link) page.

Sign in to comment.

More Answers (0)

Asked:

on 27 Jan 2018

Commented:

on 27 Jan 2018

Community Treasure Hunt

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

Start Hunting!