Discover MakerZone

MATLAB and Simulink resources for Arduino, LEGO, and Raspberry Pi

Learn more

Discover what MATLAB® can do for your career.

Opportunities for recent engineering grads.

Apply Today

How do I create dynamically changing legend entries based on an unknown number of plots?

Asked by Sarah on 20 Jul 2013

I'm writing a script that does the following:

1. Reads in data from a file that tells which data will need to be plotted.

2. Uses the data from that file to prompt the user to open another file and then plots the data from that file.

3. Once the first data set is plotted, it may prompt the user to open a second file to plot (depending on the data from the first file).

4. Everything that is plotted goes on the same plot, and the legend needs to describe each data entry.

The problem I'm running into is with the legends. If more data needs to be plotted, then I want there to be more entries in the legend. However, with the way I have it now, the first plot legend entries are overwritten when the next plot is plotted. Any ideas on how to create a dynamically changing legend? The code is included below:

AOI_unique=unique(AOI);
    %for each of those unique AOI's, plot the row vs. time and column vs.
    %time
    for i=1:length(AOI_unique)
    if AOI_unique(i)==1
        [filename_aoi_1 pathname_aoi_1]= uigetfile('~/*_AOI_1_out_Z.txt','Select the appropriate test file under **_AOI_1_out_Z.txt');
        filepath_aoi1=fullfile(pathname_aoi_1,filename_aoi_1);
          [TimeHdr CameraID1 Col1 Row1 Quality1 Width1 RA1 Dec1 Error_est1]  = textread(filepath_aoi1, repmat('%s ', 1,9),1);
          [Timecell cam1 col1 row1 qual1 wid1 ra1 dec1 error1]=textread(filepath_aoi1, '%s %f %f %f %f %f %f %f %f ', 'headerlines', 1);
          %%Plot
          plot3=plot(times,coord_pos_sibs_xfpi,'b', times,coord_pos_sibs_yfpi,'g',times,coord_pos_fpi_fov_vpa, 'r', times, col1, 'k', times, row1, 'm');
          grid on
          legend(plot3, 'coord\_pos\_sibs\_xfpi','coord\_pos\_sibs\_yfpi', 'coord\_pos\_fpi\_fov\_vpa', 'column 1', 'row 1','Location','Best');
          datetick('x','HH:MM:SS', 'keeplimits')
          title(['Position of SIBS on FPI and Row & Column vs. Time' UTtimeCharArray(1,12:19) ' --> ' UTtimeCharArray(end,12:19) ' UT '],'FontSize',10)
          xlim([min(times), max(times)])
          xlabel('Time (UT)');
          ylabel('Value');
          hold on
      elseif AOI_unique(i)==2
          [filename_aoi_2 pathname_aoi_2]= uigetfile('~/*_AOI_2_out_Z.txt','Select the appropriate test file under **_AOI_2_out_Z.txt');
          filepath_aoi2=fullfile(pathname_aoi_2,filename_aoi_2);
          [TimeHdr CameraID2 Col2 Row2 Quality2 Width2 RA2 Dec2 Error_est2]  = textread(filepath_aoi2, repmat('%s ', 1,9),1);
          [Timecell cam2 col2 row2 qual2 wid2 ra2 dec2 error2]=textread(filepath_aoi2, '%s %f %f %f %f %f %f %f %f ', 'headerlines', 1);
          %%Plot
          plot3=plot(times,coord_pos_sibs_xfpi,'b', times,coord_pos_sibs_yfpi,'g',times,coord_pos_fpi_fov_vpa, 'r', times, col2, 'k', times, row2, 'm');
          grid on
          legend(plot3, 'coord\_pos\_sibs\_xfpi','coord\_pos\_sibs\_yfpi', 'coord\_pos\_fpi\_fov\_vpa', 'column 2', 'row 2','Location','Best');
          datetick('x','HH:MM:SS', 'keeplimits')
          title(['Position of SIBS on FPI and Row & Column vs. Time' UTtimeCharArray(1,12:19) ' --> ' UTtimeCharArray(end,12:19) ' UT '],'FontSize',10)
          xlim([min(times), max(times)])
          xlabel('Time (UT)');
          ylabel('Value');
          hold on
      elseif AOI_unique(i)==3
          [filename_aoi_3 pathname_aoi_3]= uigetfile('~/*_AOI_3_out_Z.txt','Select the appropriate test file under **_AOI_3_out_Z.txt');
          filepath_aoi3=fullfile(pathname_aoi_3,filename_aoi_3);
          [TimeHdr CameraID3 Col3 Row3 Quality3 Width3 RA3 Dec3 Error_est3]  = textread(filepath_aoi3, repmat('%s ', 1,9),1);
          [Timecell cam3 col3 row3 qual3 wid3 ra3 dec3 error3]=textread(filepath_aoi3, '%s %f %f %f %f %f %f %f %f ', 'headerlines', 1);
          %%Plot
          plot3=plot(times,coord_pos_sibs_xfpi,'b', times,coord_pos_sibs_yfpi,'g',times,coord_pos_fpi_fov_vpa, 'r', times, col3, 'k', times, row3, 'm');
          grid on
          legend(plot3, 'coord\_pos\_sibs\_xfpi','coord\_pos\_sibs\_yfpi', 'coord\_pos\_fpi\_fov\_vpa', 'column 3', 'row 3','Location','Best');
          datetick('x','HH:MM:SS', 'keeplimits')
          title(['Position of SIBS on FPI and Row & Column vs. Time' UTtimeCharArray(1,12:19) ' --> ' UTtimeCharArray(end,12:19) ' UT '],'FontSize',10)
          xlim([min(times), max(times)])
          xlabel('Time (UT)');
          ylabel('Value');
          hold on
end
    i=i+1;
end

0 Comments

Sarah

Tags

Products

No products are associated with this question.

2 Answers

Answer by dpb on 23 Jul 2013
Accepted answer

I modified your practice code a little...it keeps the legends in order for those used for the SWITCH construct--there's not enough information given to know who to use for the added cases at the bottom but do the same thing...I presume you have some way to know who's who when you add the additional cases.

If it were me, I'd also throw the linestyles into an array and select from it and rearrange the data to use either named fields or cell arrays or something to eliminate the CASEs full of identical code other than some data values but that's not significant re: the ? on legends and keeping them consistent w/ the data. (I did do some more looking at it and there is no easy way to simply add an additional label to an existing legend that I could come up with anyway that was any more straightforward than the equivalent of this.)

You could keep an array of the legend strings and then just build an index vector to them to use to pass to the legend() call instead of building the string array as here. There are many ways to skin the same cat... :)

strvcat() is deprecated but I had a brain cramp and couldn't get the cell concatenation alone to work the way I wanted it to--I ended up w/ an array of cell arrays every time instead of a cell array of characters so I just reverted to old friends... :)

hplt=[];
lgnd={};
first=true;
for i=1:length(uAOI)
switch uAOI(i)
  case 1
    hplt=[hplt;plot(x1,y1,'b*')];
    lgnd=cellstr(strvcat(lgnd{:},'first'));
    legend(hplt,lgnd)
    if first, hold on, grid on, first=false; end
  case 2
    hplt=[hplt;plot(x2,y2,'r*')];
    lgnd=cellstr(strvcat(lgnd{:},'second'));
    legend(hplt,lgnd)
    if first, hold on, grid on, first=false; end
  case 3
    hplt=[hplt;plot(x3,y3,'k*')];
    lgnd=cellstr(strvcat(lgnd{:},'third'));
    legend(hplt,lgnd)
    if first, hold on, grid on, first=false; end
  case 4
    hplt=[hplt;plot(x4,y4,'c*')];
    lgnd=cellstr(strvcat(lgnd{:},'fourth'));
    legend(hplt,lgnd)
    if first, hold on, grid on, first=false; end
  end
end
% add some more
% No info on who to use for legends given...
%hplt(end+1)=plot(newx,newy);
% following will not be what is desired, though
%hleg(end+1)=legend(hplt,'first','second', 'third', 'fourth');

2 Comments

Sarah on 23 Jul 2013

Thank you for looking at this. Because I don't know which legends I will want at the end, I can't use the hleg(end+1)=legend(hplt, 'first','second','third','fourth'); I actually figured out something else that works:

 AOI_unique=unique(AOI);
    %for each of those unique AOI's, plot the row vs. time and column vs.
    %time
    for i=1:length(AOI_unique)
    if AOI_unique(i)==1
        [filename_aoi_1 pathname_aoi_1]= uigetfile('~/*_AOI_1_out_Z.txt','Select the appropriate test file under **_AOI_1_out_Z.txt');
        filepath_aoi1=fullfile(pathname_aoi_1,filename_aoi_1);
        [TimeHdr CameraID1 Col1 Row1 Quality1 Width1 RA1 Dec1 Error_est1]  = textread(filepath_aoi1, repmat('%s ', 1,9),1);
        [Timecell cam1 col1 row1 qual1 wid1 ra1 dec1 error1]=textread(filepath_aoi1, '%s %f %f %f %f %f %f %f %f ', 'headerlines', 1);
        %%Plot
        hplt=plot(times, col1, 'k', 'DisplayName', 'column 1');
        hold on
        hplt=plot(times, row1, 'm', 'DisplayName', 'row 1');
        hold on
        hleg=legend('-DynamicLegend');
        set(hleg, 'FontSize',13);
        grid on
    elseif AOI_unique(i)==2
        [filename_aoi_2 pathname_aoi_2]= uigetfile('~/*_AOI_2_out_Z.txt','Select the appropriate test file under **_AOI_2_out_Z.txt');
        filepath_aoi2=fullfile(pathname_aoi_2,filename_aoi_2);
        [TimeHdr CameraID2 Col2 Row2 Quality2 Width2 RA2 Dec2 Error_est2]  = textread(filepath_aoi2, repmat('%s ', 1,9),1);
        [Timecell cam2 col2 row2 qual2 wid2 ra2 dec2 error2]=textread(filepath_aoi2, '%s %f %f %f %f %f %f %f %f ', 'headerlines', 1);
        %%Plot
        hplt=plot(times, col2, 'c', 'DisplayName', 'column 2');
        hold on
        hplt=plot(times, row2, 'y', 'DisplayName', 'row 2');
        hold on
        hleg=legend('-DynamicLegend');
        set(hleg, 'FontSize',13);
        grid on
    elseif AOI_unique(i)==3
        [filename_aoi_3 pathname_aoi_3]= uigetfile('~/*_AOI_3_out_Z.txt','Select the appropriate test file under **_AOI_3_out_Z.txt');
        filepath_aoi3=fullfile(pathname_aoi_3,filename_aoi_3);
        [TimeHdr CameraID3 Col3 Row3 Quality3 Width3 RA3 Dec3 Error_est3]  = textread(filepath_aoi3, repmat('%s ', 1,9),1);
        [Timecell cam3 col3 row3 qual3 wid3 ra3 dec3 error3]=textread(filepath_aoi3, '%s %f %f %f %f %f %f %f %f ', 'headerlines', 1);
        %%Plot
        hplt=plot(times, col3, 'k:', 'DisplayName', 'column 3');
        hold on
        hplt=plot(times, row3, 'y:', 'DisplayName', 'row 3');
        hold on
        hleg=legend('-DynamicLegend');
        grid on
 end
    i=i+1;

This adjusted the legend, depending on the plot. Thank you again for working on this. This code would work well if I knew before which AOI's were going to plot.

dpb on 23 Jul 2013

_ ... I actually figured out something else that works:_

hplt=plot(times, col1, 'k', 'DisplayName', 'column 1');
... 
hleg=legend('-DynamicLegend');
...

Well, that's actually pretty kewl...where did you find/learn about that? I can find no reference to the 'DisplayName' property for plot() nor '-DynamicLegend' for legend but it seems to work for R2012b.

OK, on R12 I get a 'no such property for line object' -- oh, there it is the line object properties it's a new feature at some point.

I agree, for your situation or others like it, it's clearly the cat's meow...I wasn't aware 'DisplayName' existed.

dpb
Answer by dpb on 20 Jul 2013

Save the handles to both the plots and the legend and augment them each pass.

hplt=plot(x,y);
hleg=legend('first');
...
% add some more
hold on
hplt(end+1)=plot(newx,newy);
hleg(end+1)=legend(hplt,'first','second');

Using the handle array ensures the line style/color will match that of the corresponding line.

Rather than using fixed character strings as in the toy example, use an array and augment it as desired.

BTW, please accept answer on the "how to label time-axis ticks" for scoring...or explain what's lacking in the solution for your purposes. :)

4 Comments

dpb on 22 Jul 2013

Yes, as said, build the full array of text strings needed (one for each line) and call legend to write the whole thing again (this will keep the linestyles in synch automagically)

Save each label string as a new line in a cell array each time you add a new plot. Then you'll always have the same number as the number of plots. When you start over a new plot; clear the array.

Sarah on 22 Jul 2013

I tried working with your example and created some code. I'm not sure if this came across, but not all legend entries need to be displayed, only those for the AOI #, which was referenced in the text file. These are unknown when running the script originally. I don't know if this changes your answer. Here's the code I tried:

%legendPractice.m
x1=[1 1 2];
y1=[2 3 4];
x2=[4 5 6];
y2=[3 3 3];
x3=[6 7 7];
y3=[3 2 1];
x4=[9 9 8];
y4=[2 3 3];
newx=[0 0 0];
newy=[0 0 0];
AOI=[0 0 0 0 0 2 2 2 2 2 2 0 4 4 4 4 4 0 0 0 0 0 0 0 0];
AOI_unique=unique(AOI);
for i=1:length(AOI_unique)
    if AOI_unique(i)==1
hplt=plot(x1,y1,'b*');
hleg=legend('first');
grid on
hold on
      elseif AOI_unique(i)==2
          hplt=plot(x2, y2, 'r*')
          hleg=legend('second');
          grid on
          hold on
      elseif AOI_unique(i)==3
          hplt=plot(x3, y3, 'k*')
          hleg=legend('third');
          grid on
          hold on
      elseif AOI_unique(i)==4
          hplt=plot(x4, y4, 'c*')
          hleg=legend('fourth');
          grid on
          hold on
      end
  end
% add some more
hold on
hplt(end+1)=plot(newx,newy);
hleg(end+1)=legend(hplt,'first','second', 'third', 'fourth');

In this example, the legends are 'first' and 'second', when they should be 'second' and 'fourth'. Also, to use this, do you have to have a final plot that's going to plot?

dpb on 22 Jul 2013

...not all legend entries need to be displayed, only those for the AOI #, which was referenced in the text file. These are unknown when running the script originally

Well, you have to know somehow which ones you want labelled what--select the appropriate text string and use it.

...the legends are 'first' and 'second', when they should be 'second' and 'fourth'

Again, only put the ones that go with the lines that are on the plot into the array.

to use this, do you have to have a final plot that's going to plot?

I'm suggesting each time you add a line you should know what the labels are for the preceding lines ('cuz you saved them when they were plotted) and the present one (and you're adding it now). When have that after the line is added, then you should have an array of text legends that corresponds 1:1 w/ the lines w/ the correct labels. legend() w/ that array will redraw w/ the correct number and names.

Why isn't it (at least relatively) simple to create the list of titles corresponding to the data you're plotting? What am I missing here?

dpb

Contact us