taskGNU Octave - Tasks: task #14243, Rewrite legend.m

 
 

You are not allowed to post comments on this tracker with your current authentication level.

task #14243: Rewrite legend.m

Submitted by:  Pantxo Diribarne <pantxo>
Submitted on:  Thu 17 Nov 2016 10:11:23 AM UTC  
Votes:  2  
 
Should Start On:  Thu 17 Nov 2016 12:00:00 AM UTC Should be Finished on:  Thu 17 Nov 2016 12:00:00 AM UTC
Category:  Wish Priority:  5 - Normal
Status:  Ready For Test Privacy:  Public
Assigned to:  None Originator Name: 
Open/Closed:  Open Fixed Release:  None
Planned Release:  None

( Jump to the original submission)

Mon 18 Nov 2019 09:20:49 PM UTC, comment #24: 

Just got a build on Linux ready. AFAICS stretching of the legend when resizing plot windows is no more - good!

Philip Nienhuis <philipnienhuis>
Project Member
Mon 18 Nov 2019 07:14:57 PM UTC, comment #23: 

@Philip: Thanks.

>> Is bug #49446 (about stretching the legend when resizing the plot window and dragging it around by mouse) also tackled?


AFAIU this bug is mainly about being able to manipulate legend objects with the mouse, i.e. implementing the selection tool. Nothing new on that front.

Now about the legend box being stretched or compressed when resizing the plot window, those ones (somewhere between bug #39697, bug #48859, and bug #50094) are ready for test.

Pantxo Diribarne <pantxo>
Project Member
Mon 18 Nov 2019 07:02:35 PM UTC, comment #22: 

By the way: an impressive piece of work, Panxto, thank you very much!

Philip Nienhuis <philipnienhuis>
Project Member
Mon 18 Nov 2019 07:01:38 PM UTC, comment #21: 

Q: (haven't built yet)
Is bug #49446 (about stretching the legend when resizing the plot window and dragging it around by mouse) also tackled?

Philip Nienhuis <philipnienhuis>
Project Member
Mon 18 Nov 2019 09:53:18 AM UTC, comment #20: 

@Rik: Thanks. I fixed the latest reported issues, improved the autoupdate code, and pushed the patch here:

http://hg.savannah.gnu.org/hgweb/octave/rev/c66467f74278

I posted a new bug in which we can handle the updates to the documentation

https://savannah.gnu.org/bugs/?57254

I also marked the following legend bugs bugs ready for test:

bug #50038, bug #40333, bug #39697, bug #56787, bug #48859, bug #42940, bug #33112, bug #50094, bug #41142, bug #56972, bug #55379.

Pantxo Diribarne <pantxo>
Project Member
Thu 14 Nov 2019 06:30:42 PM UTC, comment #19: 

I think this is very close to having sufficient functionality that you can check the code in and we can continue development through ordinary bug reports as any problems are found.

Before that, I did find the following regressions with the existing code.

Case #1 : "Orientation" property does not work after initial usage

Code to reproduce:

plot (rand (2, 4));
hl = legend ("orientation", "horizontal")
set (hl, "orientation", "vertical")

The "orientation" should switch, but it remains fixed at the initial value even though the property itself is changed as can be checked by executing

get (hl, "orientation")

Case #2 : Font properties lost after hide/show sequence

plot (1:10);
hl = legend ("Hello World", "FontSize", 24);
legend hide
legend show

Case #3 : Spacing between axes text (xlabel, title, etc.) and legend is order-dependent.

This one might be a long-term bug that is harder to fix.

Code to reproduce:

plot (1:10);
hl = legend ("Hello World", "FontSize", 24, "location", "northoutside");
title ({"Line1", "Line2", "Line3"});

In this case the legend overlaps the multi-line title.  However, if the title is created before the legend then everything works. 

plot (1:10);
title ({"Line1", "Line2", "Line3"});
hl = legend ("Hello World", "FontSize", 24, "location", "northoutside");

Case #4 : setting "String" property results in callback execution error

Code to reproduce:

plot (1:10, 'o-')
hl = legend ('abc')
set (hl, 'string', 'Hello World')
execution error in graphics callback function

Case #5 : "bestoutside" location is incorrect for vertically oriented legends

When "bestoutside" is specified, the mapped location should be "northeastoutside" if the legend is vertically oriented and "southoutside" if the legend is horizontally oriented.  Currently, both orientations are mapped to "southoutside".

plot (rand (2,4))
hl = legend ('location', 'bestoutside')

Case #6 : No warning that location "best" is not currently implemented.

"best" should place the legend object so that it overlaps the least with the data being plotted.  There used to be a warning that Octave hasn't implemented this property yet, and then the legend was placed in location "northeast".

Code to reproduce:

plot (1:10, 'o-');
hl = legend ("Hello World", "Location", "best")

The old warning was

warning: legend: 'best' not yet implemented for location specifier, using 'northeast' instead

Case #7: Setting "position" should change "location" to "none"

plot (1:10, 'o-')
hl = legend ('abc');
set (hl, 'position', [0.2, 0.2, 0.1, 0.1])
get (hl, 'location')
ans = northeast

Rik <rik5>
Project Administrator
Wed 13 Nov 2019 08:58:54 AM UTC, comment #18: 

I attached an updated cset that improves performances a bit and fixes a bunch problems.

(file #47857)

Pantxo Diribarne <pantxo>
Project Member
Tue 12 Nov 2019 04:35:53 PM UTC, comment #17: 

I came to the same conclusion about _appdata_ being a different issue and filed bug #57212 to track it.

Yesterday, was a holiday in the U.S. so I had plenty of time to code.  I probably won't be able to review until possibly tomorrow.

However, with all the fixes I think we are very quickly converging on a replacement.

For the question on Matlab behavior regarding more than 20 labels, I checked it a day or so ago and it was accurate, but today it isn't.  Maybe they updated something.  In any case, it is easier if we don't have to check.  They still document the old behavior (https://www.mathworks.com/help/matlab/ref/legend.html)

To label more than 20 objects in the legend, specify a label for each object. Otherwise, legend depicts only the first 20 objects in the graph

Rik <rik5>
Project Administrator
Tue 12 Nov 2019 04:17:55 PM UTC, comment #16: 

Looks like the zip archive is too large.

(file #47844, file #47845, file #47846, file #47847)

Pantxo Diribarne <pantxo>
Project Member
Tue 12 Nov 2019 04:13:43 PM UTC, comment #15: 

Trying again to attach


Pantxo Diribarne <pantxo>
Project Member
Tue 12 Nov 2019 04:11:41 PM UTC, comment #14: 

I attached a figure that shows the performance drop when plotting and printing legend demos. In lies between 20% (most often) and more than 100% in some cases.
I also attached a zipped archive in which you will find the printed demos. Open compare_plot_demos_04.html to see the comparison.

Pantxo Diribarne <pantxo>
Project Member
Tue 12 Nov 2019 11:56:57 AM UTC, comment #13: 

@Rik: Here is an updated patch.

  • Case #1: Now ok;
  • Case #2: That is not what I observe when testing online: Matlab adds a legend with as many item as plots in the axes. In ML, the legend then extends below the figure limits while in Octave it is layout in two columns to fit in the axes box.
  • Case #3: I'd like to remove this possibility of specifying the location through numeric values
  • Case #3 bis: Now ok;
  • Case #4, 5, 6, 7, 8, 9, 10, 11, 12: Now ok;
  • Case #13, 14: Yes, I still have to find a reliable way to handle plotyy; The new patch improves the situation.
  • Case #15: I don't know how to handle outside legends in subplots: by default subplots are supposed to be aligned, which is what the listeners in subplot.m try to ensure. This is the reason why the narrowest axes (due to its long legend) dictates the size of other axes. This looks consistent to me (and straightforward) and I would like to avoid having to make the subplot.m code "legend aware". Note that previously in Octave, outside legends would simplpy break the subplot auto-alignment feature, which is not better...
  • Case #16: I added back demo 36
  • Case #17: That is not what I observe, the average after running demo 14 ten times is roughly the same: New code 0.265s, Old code 0.280s.
  • Case #18: I reduced the item size to 15pts
  • Case #19: I agree the _appdata_ should be hidden, but I think this is outside the scope of this task and I'd prefer to handle that in a separate bug report.
  • Case #20: Now ok.

Finally the new patch uses an additional line object to center the markers.

(file #47841)

Pantxo Diribarne <pantxo>
Project Member
Tue 12 Nov 2019 12:17:50 AM UTC, comment #12: 

I'm starting to recall that legend is a very intricate function with lots of small behavior deviations.  The good news is that the second patch is much faster, and about the same timing as what we have today when used on ordinary plots.

Some additional corner cases that I discovered.

Corner case #1 (incorrectly handling multiple text strings)

x = linspace (0, 2*pi, 100);
plot (x, sin (x), 'o-', x, cos (x), '*-')
legend ("sine", "cosine");
error: binary operator '/' not implemented for 'cell' by 'scalar' operations
error: called from
    legend>__legend_textitem_array__ at line 841 column 14
    legend>__legend_update_layout__ at line 632 column 31
    legend at line 201 column 5
    /tmp/oct-tKWrbi at line 3 column 6

Corner case #2 (labeling more than 20 data objects)

The behavior quoted below is from the existing legend implementation.  Like Matlab, if the default number of labels is greater than 20 then it issues a warning and only labels the first 20.  This behavior is no longer present in the patch.

octave:2> plot (rand (30));
octave:3> legend ()
warning: legend: labeling only first 20 data objects
warning: called from
    legend at line 450 column 11

Corner case #3 (doesn't accept a numeric value for "location")

This could be behavior that we just drop.  Specifying location with a numeric value has been deprecated for a very long time, and is no longer supported in Matlab either.

octave:1> plot (1:10, 'o-')
octave:2> legend ('abc', 'location', 3)
error: set: invalid value for radio property "location"
error: called from
    legend at line 195 column 5

Corner case #3 (legend object created even when function fails)

This is a problem because it creates an axes object with tag "legend" which leaves a white rectangle on the plot.  If input validation fails there should be no object created.  Maybe you want to surround the construction of the axes object with a try/catch block, and if there is an error delete the created axes legend object and then re-throw the error.

octave:5> plot (1:10, 'o-')
octave:6> h = legend ('abc', 'location', 'foobar')
error: invalid value = foobar
error: called from
    legend at line 195 column 5
octave:7> findall (gcf, 'tag', 'legend')
ans = -5.6867

Corner case #4 ("bestoutside" location property not implemented)

Current legend.m implements "bestoutside" so we shouldn't regress on that behavior.

octave:10> plot (1:10, 'o-')
octave:11> h = legend ('abc', 'location', 'bestoutside')
error: invalid value = bestoutside
error: called from
    legend at line 195 column 5

Corner case #5 ("none" location property not implemented)

"none" turns off the default positioning algorithm and allows the user to set the location directly with the "position" property of the legend object.

octave:10> plot (1:10, 'o-')
octave:11> h = legend ('abc', 'location', 'none')
error: invalid value = none
error: called from
    legend at line 195 column 5

Comment #6 (Octave prefers no Camel Case properties)

The following properties should probably be all lower case to match Octave's other properties.

 AutoUpdate = off
NumColumns = 1
NumColumnsMode = auto

Corner case #7 (listener on "numcolumns"?)

The "numcolumns" property works correctly when the legend is created, but subsequent changes to the property don't re-format the legend.

x = linspace(0,pi);
y1 = cos(x);
plot(x,y1)

hold on
y2 = cos(2*x);
plot(x,y2)

y3 = cos(3*x);
plot(x,y3)

y4 = cos(4*x);
plot(x,y4)
hold off

hl = legend({'cos(x)','cos(2x)','cos(3x)','cos(4x)'},'Location','northwest','NumColumns',2);

set (hl, 'numcolumns', 1)

Corner case #8 (legend show)

When called with the argument "show", legend should display a legend as if it was called with no arguments.  This produces "data1", "data2", etc.  Instead, the new legend code is treating "show" as a text label rather than a command.  legend ("toggle") produces the same behavior.

octave:106> plot (rand (3,2))
octave:107> legend show
warning: legend: ignoring extra objects.
warning: called from
    legend>__legend_parse_opts__ at line 555 column 9
    legend at line 125 column 8

Corner case #9 (horizontal spacing)

Try

demo legend 6

and you will see that the text and keys are crowded when compared to the current version of legend.

Corner case #10 (property "textcolor" not implemented?)

Try

demo legend 9

The text should be magenta color rather than black.

Corner case #11 (demo #15, #16 produce warnings when they shouldn't)

legend example 15:
clf;
x = linspace (0, 10);
plot (x, x);
hold on;
stem (x, x.^2, "g");
title ("First created object gets first label");
legend ("linear");
hold off;

warning: legend: ignoring extra objects.
warning: called from
    legend>__legend_parse_opts__ at line 555 column 9
    legend at line 125 column 8
    __demo__ at line 9 column 2
    demo at line 147 column 7

Corner case #12 (demo #22 produces only one label)

Try

demo legend 22

It should produce a legend with "sin (x)" and "cos (x)" as labels.

Corner case #13 (callback execution error when running demo #28)

This occurred while running all of the demos in legend.m

Press <enter> to continue:
execution error in graphics callback function
legend example 28:
clf;
x = 0:10;
y1 = rand (size (x));
y2 = rand (size (x));
[ax, h1, h2] = plotyy (x, y1, x, y2);
ylabel (ax(1), {"Blue", "Y", "Axis"});
title ('plotyy legend test #2: "westoutside" adjusts to ylabel');
drawnow ();
legend ([h1, h2], {"Blue", "Orange"}, "location", "westoutside");

Corner case #14 (demo #29 fails)

Try

legend demo 29

The rightside axes is messed up.

Corner case #15 (demo #30)

In the new legend code, the subplots are all set to the same size which accommodates the largest of the text strings.  In previous versions of Octave, in Matlab, each subplot is re-sized to fit the length of the label it is paired with.

Try

legend demo 30

Comment #16 (demo #36 missing)

Demo #36 is missing in the new code.  I think it is correct to include it because it shows that the problem has now been fixed.  If there is a regression in behavior this demo will show it.

Comment #17 (performance measure)

I used demo #14 which does not include subplots to show that the new code can be significantly slower even in an ordinary case.

tic; demo ("legend", 14); toc

Old code: 0.5 seconds
New code: 2.3 seconds

Comment #18 (length of lines in legend key)

I used

demo legend 3

and then measured the length of the lines in the legend key.  In the old code it is about 20 pixels and in the new code it is about 27 pixels.  I like the shorter length.  Matlab uses long line lengths and I think it is ugly.

Comment #19 (hide _appdata_ property?)

The _appdata_ property is for internal use only.  It should probably be a hidden property so that users don't have to see all the guts of the implementation.

octave:149> plot (1:10, 'o-')
octave:150> hl = legend ('abc')
hl = -102.66
octave:151> get (hl)
ans =

  scalar structure containing the fields:

    __appdata__ =

      scalar structure containing the fields:

        __axes_handle__ = -108.94
        __next_label_index__ = 1
        __peer_objects__ = -103.14
        __updating_layout__ = 0
        __original_looseinset__ =

           0.130000   0.110000   0.095000   0.075000

        __original_units__ = normalized
        __peer_axes_position__ =

            54.587    34.641   325.420   256.662

        __listeners__ =
        {
          [1,1] =
          {
            [1,1] = 1
            [1,2] = position
            [1,3] =
            {
              [1,1] = @__maybe_update_layout__
              [1,2] = -102.66
            }

          }

          [1,2] =
          {
            [1,1] = -108.94
            [1,2] = position
            [1,3] =
            {
              [1,1] = @__maybe_update_layout__
              [1,2] = -102.66
            }

          }

          [1,3] =
          {
            [1,1] = -108.94
            [1,2] = tightinset
            [1,3] =
            {
              [1,1] = @__maybe_update_layout__
              [1,2] = -102.66
            }

          }

          [1,4] =
          {
            [1,1] = -108.94
            [1,2] = colormap
            [1,3] =

@(hax) set (hl, "colormap", get (hax, "colormap"))

          }

          [1,5] =
          {
            [1,1] = -108.94
            [1,2] = fontsize
            [1,3] =

@(hax) set (hl, "fontsize", 0.9 * get (hax, "fontsize"))

          }

          [1,6] =
          {
            [1,1] = -108.94
            [1,2] = children
            [1,3] =
            {
              [1,1] = @__legend_autoupdate__
              [1,2] = -102.66
            }

          }

        }


    AutoUpdate = off
    edgecolor =

       0.1500   0.1500   0.1500

    interpreter = tex
    itemhitfcn = [](0x0)
    location = northeast
    NumColumns = 1
    NumColumnsMode = auto
    orientation = vertical
    string =
    {
      [1,1] = abc
    }

Corner case #20 ("EdgeColor" not implemented?)

Using this property doesn't seem to work anymore.

plot (1:10, 'o-');
hl = legend ('abc');
set (hl, 'edgecolor', 'r')

Rik <rik5>
Project Administrator
Mon 11 Nov 2019 06:03:11 PM UTC, comment #11: 

@Rik: Thanks for the review. I attached an updated version that fixes item 3 through 6, still WIP.

For item 2, I agree that a single marker in the middle is nicer but this also means 2 graphics objects, one for the line, one for the marker. I'll update the code along this idea unless someone has a better one.

As for performance (item 1), the new patch improves the situation   (still slower than before) but the trade off is that after a legend has been created, its layout (the way text and item are arranged in an array) won't be updated when the axes or the figure size changes. This is not dramatic but it makes the auto-arrange feature (Octave specific) only work when creating the legend:

## A plot with many lines
plot (rand (30));
## Create a legend that nicely fits within the axes
legend ()

I'll try to look for places where performance can be improved, but note that the new function is already faster that the previous one in many situations (except in subplots where the position is updated very often).

(file #47838)

Pantxo Diribarne <pantxo>
Project Member
Sun 10 Nov 2019 03:45:22 AM UTC, comment #10: 

If this gets checked in you should be able to close a large number of bug reports that all relate to legend not adjusting in size correctly with changes in the axes and figure.  I just came across another one: bug #56972.

Rik <rik5>
Project Administrator
Sun 10 Nov 2019 02:44:12 AM UTC, comment #9: 

@Pantxo: I reviewed the code a bit (not fully).  It's quite good in that it solves the longstanding issue where the legend position was not updated with size changes to the axes.  For example,

plot (1:10, 'o-');
legend ({"legend text"});
title ({"line 1"; "line 2"; "line 3"; "line 4"});

The hack to handle gnuplot is ugly, but I think acceptable.

Some things I did notice:

1) Performance is much slower (~1/2)

Try this code

tic; demo ("legend", 31); toc

With the new code it took 6.7 seconds, while the old code took just 3.0 seconds.  Either one is pretty unacceptable, but users will really notice a nearly 7 second delay.

You could use the Octave profiler to see where the code is spending its time.  I think the old version of legend.m used lots of tricks such as turning the visibility of the legend off while it was being constructed and only turning it on at the end, and disabling the listener functions while it was updating.

2) legend key for line objects

The old legend key for a line object had the line, in the correct color, with the marker displayed in the midpoint of the line.  This is the style Matlab uses as well and I like it.  The style in this patch, with a marker on the left and on the right and a line connecting them, seems too busy.

3) legend ("text") fails

The most common calling form fails.  Example code:

octave:1> plot (1:10, 'o-')
octave:2> legend ('abc')
error: scalar cannot be indexed with {
error: called from
    legend>__legend_textitem_array__ at line 814 column 16
    legend>__legend_update_layout__ at line 623 column 27
    legend at line 201 column 5

4) No warning about extra arguments to legend

Try

demo legend 26

This should print a warning about calling legend incorrectly.

5) Support old multiple output calling form.

The old calling form was

[HLEG, HLEG_OBJ, HPLOT, LABELS] = legend (...)

If you use this format in Matlab, it returns the data it can, HLEG and HPLOT, and empty objects for HLEG_OBJ and LABELS.  It might be nice to replicate that since it will be a big step change for users to switch all of their code to the new syntax.

[HLEG, HPLOT] = legend (...)

6) stray keyboard statement

There is a stray keyboard statement at line 341 that you must have been using for testing.  I just commented it out in order to run tests.

Rik <rik5>
Project Administrator
Mon 28 Oct 2019 05:15:45 PM UTC, comment #8: 

I forgot to mention the ugly hack ... since I know nothing about gnuplot, I renamed the old legend.m code to _gnuplot_legend_.m and call this function when gnuplot is the graphics toolkit.

Pantxo Diribarne <pantxo>
Project Member
Mon 28 Oct 2019 05:10:55 PM UTC, comment #7: 

I attached a WIP ersion of a complete rewrite of legend.m. Could you give me some feedback.

AFAICS, all the bugs that are mentioned in this task are solved by the new implementation and I checked that all previous demos still work.

TODO:

  • testing
  • testing
  • testing
  • Better documentation, including for legend specific properties:

If we don't want the main help string to become huge, we probably need to have a separate page for properties, as we do for primitive graphics objects

  • Implement the numcolumns property:
  • Better guard axes position updates against too large outside legends
  • polish the code
 

(file #47760)

Pantxo Diribarne <pantxo>
Project Member
Tue 26 Dec 2017 11:58:06 PM UTC, comment #6: 

I eliminated the questionable use of the "userdata" property to hold internally private legend data in this cset (http://hg.savannah.gnu.org/hgweb/octave/rev/19d63669d174).

Rik <rik5>
Project Administrator
(Why is this post is considered to be spam? Users may have reported it to be spam or, if it has been recently posted, it may just be waiting for spamchecks to be run.)
Spam content:

OK, a lot of work, but I think any new minor bugs are worth the change.

This conditional:

+verbatim+
+ if (! all (ishghandle (plty)))
error ("legend.m: This should not happen. File a bug report.");
endif
-verbatim-

All it is really doing is encouraging users to file a bug report. It creates an error with regard to Octave's internal programming, not something caused by the user (like an ill-formed matrix, etc.). But without this it will result in an error eventually also via using a bogus handle. So, I don't know how beneficial this was in the first place. If it is possible the user inputs a bogus handle, then it is O.E. and we don't want a bug report for that.

Missed line-space here?:
+verbatim+
+
+endfunction
+## Axes to which legend was attached has been deleted. Delete legend.
+function cb_axes_deleted (~, ~, hlegend)
-verbatim-
Dan Sebald <sebald>
Thu 21 Dec 2017 12:21:48 PM UTC, comment #5: 

Not that it is relevant for this task, but I significantly rewrote legend.m for greater Matlab compatibility in this cset (http://hg.savannah.gnu.org/hgweb/octave/rev/436deae9f9ab).

Rik <rik5>
Project Administrator
Wed 13 Dec 2017 08:15:07 PM UTC, comment #4: 

Investigating bug #52641 led me to look more closely at legend.m.  My suggestion is to make this more listener/callback oriented and get rid of some of the old-convention techniques like persistent variables, etc.  If one gets in the listener/callback frame of mind it becomes much easier to realize simpler ways of doing things.

1) Drop the use of "userdata" everywhere. Not only is it legend.m where this "userdata" variable is being used to store things.  "UserData" is for the user to use as desired.  Even if some of these objects typically don't have a UserData counterpart in Matlab, if the user decides to use some legend "userdata" it will disrupt the legend.

2) Make the listeners more abundant and at the same time smaller hunks. That is the typical strategy of callback-based frameworks. The behavior lies in all the connections made. For example, rather than reconstructing the legend via calling legend() again and again, there could be listeners tied to the axes children list. If the children list changes (due to addition of another plot item, e.g., "hold on") it could trigger a reconstruction of the plot. Listeners could be tied to the name text objects such that if one of those text objects changes--either directly or via "set(hax, 'displayname', 'newname')" which in itself triggers some callbacks--it will trigger a re-layout of the plot.

3) The way that the current-axes and legend-handle are derived seems far more complex than it needs to be. If the input is a valid axes handle, use that, otherwise gca() does the job, even constructing the axes if they don't exist. All that searching of the ancestor tree should be replaced by something far more simple.

4) With regard to the reoccurring scanning of ancestors and kids, it might be better to place more information in the list of arguments to listeners. It makes the number of variables passed around greater, but at the same time it saves running a hunk of code to reconstruct that info.

Dan Sebald <sebald>
Wed 23 Nov 2016 11:03:45 AM UTC, comment #3: 

Here are a few other tests inspired by the current demos

%% Test the current demos work
%% demo 1: currently fails because text object are deleted/recreated
hf = figure ('visible', 'off');
plot (rand (2));
[hl, hicon] = legend ({"foo"}, "bar");
legend (hl, "location", "northeastoutside");
set (hl, "fontsize", 20);
ht = hicon(strcmp (get (hicon, "type"), "text"));
assert (get (hl, "string"), {"foo", "bar"});
assert (get (ht, "string"), {"foo"; "bar"});
assert (get (ht, "fontsize"), [20; 20])
assert (get (hl, "location"), "northeastoutside")
close (hf);

%% demo 2:
hf = figure ('visible', 'off');
plot (rand (3));
hl = legend ("show");
assert (get (hl, "string"), {"data1", "data2", "data3"})
close (hf);

%% demo 3: fails because legend without arguments doesn't return hicon
hf = figure ('visible', 'off');
x = 0:1;
hlines = plot (x,x,";I am Blue;", x,2*x, x,3*x,";I am yellow;");
[hl, hicon] = legend ();
hleglines = hicon(strcmp (get (hicon, "type"), "line"));
assert (get (hlines(3:-2:1), "color"), get (hleglines, "color"));
close (hf);

%% demo 4:
hf = figure ('visible', 'off');
plot (1:10, 1:10, 1:10, fliplr (1:10));
hl = legend ({"I am blue", "I am orange"}, "location", "east");
legend ("hide")
assert (get (hl, "visible"), "off")
hl2 = legend ("show");
assert (get (hl2, "visible"), "on")
assert (hl, hl2)
close (hf);

%% demo 5:
hf = figure ('visible', 'off');
plot (1:10, 1:10, 1:10, fliplr (1:10));
[hl, hicon] = legend ({"I am blue", "I am orange"}, ...
                      "location", "east", "orientation", "horizontal");
ht = hicon(strcmp (get (hicon, "type"), "text"));
assert (get (ht(1), "position")(1) < get (ht(2), "position")(1))
legend boxoff
assert (get (hl, "box"), "off")
legend boxon
assert (get (hl, "box"), "on")
close (hf);

%% demo 7:
hf = figure ('visible', 'off');
plot (1:10, 1:10, 1:10, fliplr (1:10));
legend ({"I am blue", "I am orange"}, "location", "east");
[hl, hicon] = legend ("left");
ht = hicon(strcmp (get (hicon, "type"), "text"));
hlines = hicon(strcmp (get (hicon, "type"), "line"));
assert (get (ht(1), "position")(1) < get (hlines(1), "xdata")(1))
assert (get (ht(2), "position")(2) < get (hlines(2), "xdata")(2))
close (hf);

%% demo 8
hf = figure ('visible', 'off');
plot (1:10, 1:10, 1:10, fliplr (1:10));
[hl hicon] = legend ({"I am blue", "I am orange"}, "location", "east");
ht = hicon(strcmp (get (hicon, "type"), "text"));
set (hl, "textcolor", [1 0 1]);
assert (get (ht, "color"), {[1 0 1]; [1 0 1]});
close (hf);

%% demo 14
hf = figure ('visible', 'off');
hlines = plot (rand (2));
[hl hicon] = legend ("foo");
ht = hicon(strcmp (get (hicon, "type"), "text"));
hlegline = hicon(strcmp (get (hicon, "type"), "line"));
assert (get (ht, "string"), get (hlines(1), "displayname"))
assert (get (hlegline, "color"), get (hlines(1), "color"))
close (hf);

%% demo 17
hf = figure ('visible', 'off');
subplot (2,1,1);
rand_2x3_data1 = [0.341447, 0.171220, 0.284370; 0.039773, 0.731725, 0.779382];
hbars = bar (rand_2x3_data1);
[hl hicon] = legend ({"1st Bar", "2nd Bar", "3rd Bar"}, ...
                     "location", "northwest");
hicon = hicon(! strcmp (get (hicon, "type"), "text"));
assert (get (hicon, "type"), {"patch"; "patch"; "patch"})
assert (get (hicon, "facecolor"), get (hbars, "facecolor"))
subplot (2,1,2);
x = linspace (0, 10, 20);
hstem(1) = stem (x, 0.5+x.*rand (size (x))/max (x), ...
                 "markeredgecolor", [0 0.7 0]);
hold on;
hstem(2) = stem (x+10/(2*20), x.*(1.0+rand (size (x)))/max (x));
[hl hicon] = legend ({"Multicolor", "Unicolor"}, "location", "northwest");
hicon = hicon(! strcmp (get (hicon, "type"), "text"));
assert (get (hicon, "type"), {"line"; "line"})
assert (get (hicon, "color"), get (hstem, "markeredgecolor"))
close (hf);

%% demo 18
hf = figure ('visible', 'off');
surf (peaks ());
[hl hicon] = legend ("peaks()");
hicon = hicon(! strcmp (get (hicon, "type"), "text"));
assert (get (hicon, "type"), "patch")
close (hf);

%% demo 21/22/23
hf = figure ('visible', 'off');
x = 0:0.1:10;
plot (x, sin (x), ";sin (x);");
hold on;
plot (x, cos (x), ";cos (x);");
[hl hicon] = legend ();
assert (get (hl, "string"), {"sin (x)", "cos (x)"});
hl = legend ({"Sine", "Cosine"}, "location", "northeastoutside");
assert (get (hl, "string"), {"Sine", "Cosine"});
close (hf);

%% demo 24
hf = figure ('visible', 'off');
plot (1:10);
hl = legend ("foo");
legend off
assert (! ishandle (hl))
close (hf);

%% demo 25
hf = figure ('visible', 'off');
plot (rand (2));
legend ("Hello", "World", "interpreter", "foobar");
assert (lastwarn (), "legend: ignoring extra labels")
close (hf);

%% demo 26/27/28
hf = figure ('visible', 'off');
x = 0:10;
y1 = rand (size (x));
y2 = rand (size (x));
[hax, h1, h2] = plotyy (x, y1, x, y2);
hl = legend ("Blue", "Orange");
assert (get (hl, "string"), {"Blue", "Orange"})
hl = legend ({"Blue", "Orange"});
assert (get (hl, "string"), {"Blue", "Orange"})
hl = legend ([h1, h2], {"Blue", "Orange"});
assert (get (hl, "string"), {"Blue", "Orange"})
close (hf);

Pantxo Diribarne <pantxo>
Project Member
Thu 17 Nov 2016 09:53:54 PM UTC, comment #2: 

I think bug #49446 boils down to implementing the selection tool in Qt/Fltk toolkits but indeed legend objects should be able to handle custom positioning properly.

As a starter I'll dump here some test scripts (written ML compatible for testing) that should pass and that currently pass or not with Octave's implementation.

%%test <39697>
hf = figure ('visible', 'on');
plot (1:10);
hl = legend ('Legend Text');
title ({'Multi-line', 'titles', 'are a', 'problem'});
pos = get (gca, 'position');
axtop = sum (pos(2:2:4));
pos = get (hl, 'position');
legtop = sum (pos(2:2:4));
assert (legtop < axtop);
close (hf);

%%test <40333>
hf = figure ('visible', 'off');
plot (1:10);
hl = legend ('foo');
pos = get (gca, 'position');
set (hf, 'position', [0 0 200 200]);
set (hl, 'fontsize', 20);
%assert (all (get (gca, 'position') == pos));
close (hf);

%%test <41142>
hf = figure ('visible', 'off');
plot (rand (2));
legend ({'foo', 'bar'});
ht = findobj (gcf (), '-property', 'fontsize');
set (ht, 'fontsize', 16);
assert (all (cell2mat (get (ht, 'fontsize')) == 16))
close (hf);

Pantxo Diribarne <pantxo>
Project Member
Thu 17 Nov 2016 06:59:06 PM UTC, comment #1: 

Can bug #49446 added to the list, please? (at least for legends)

Philip Nienhuis <philipnienhuis>
Project Member
Thu 17 Nov 2016 10:11:23 AM UTC, original submission:  

There are a couple of bug reports (that partly overlap) that show the need for (complete?) rewrite of legend.m:

  • bug #49940, bug #41142: related to the fact that legend deletes and recreates text objects when someone tries to change a single text property on the object directly
  • bug #39697, bug #39316: related to the legend object not having listeners on the figure/axes position so that it doesn't behave well when the figure or axes rescaled
  • bug #47455: building a legend is slow, updating it is also very slow since most of the time the legend is entirely rebuilt.
  • bug #40333: changing the fontsize on a legend modifies its corresponding axes position, if the latter position has changed since the legend was created
Pantxo Diribarne <pantxo>
Project Member

 

Attached Files
file #47857:  task14243_legend_4.patch added by pantxo (158KiB - text/x-patch)
file #47846:  newlegend.zip added by pantxo (1MiB - application/zip)
file #47844:  oldlegend.zip added by pantxo (1MiB - application/zip)
file #47843:  timings.png added by pantxo (33KiB - image/png)
file #47841:  task14243_legend_3.patch added by pantxo (157KiB - text/x-patch)
file #47838:  task14243_legend_2.patch added by pantxo (152KiB - text/x-patch)
file #47760:  task#14243_legend.patch added by pantxo (151KiB - text/x-patch)

 

Digest:
   bug dependencies.

Items that depend on this one: None found

 

Carbon-Copy List
  • -email is unavailable- added by rik5 (Posted a comment)
  • -email is unavailable- added by sebald (Posted a comment)
  • -email is unavailable- added by johasixt (Voted in favor of this item)
  • -email is unavailable- added by reginalduck (Voted in favor of this item)
  • -email is unavailable- added by pantxo (Submitted the item)
  •  

    Do you think this task is very important?
    If so, you can add your encouragement to it.
    This task has 2 encouragements so far.

    Only logged-in users can vote.

     

     

     

    Follow 15 latest changes.

    Date Changed by Updated Field Previous Value => Replaced by
    2019-11-18 pantxo StatusIn Progress => Ready For Test
    2019-11-13 pantxo Attached File- => Added task14243_legend_4.patch, #47857
    2019-11-12 pantxo Attached File- => Added oldlegend.zip, #47844
        Attached File- => Added compare_plot_demos_04.html, #47845
        Attached File- => Added newlegend.zip, #47846
        Attached File- => Added compare_plot_demos_05.html, #47847
    2019-11-12 pantxo Attached File- => Added timings.png, #47843
    2019-11-12 pantxo Attached File- => Added task14243_legend_3.patch, #47841
    2019-11-11 pantxo Attached File- => Added task14243_legend_2.patch, #47838
    2019-11-10 rik5 CategoryNone => Wish
        StatusNone => In Progress
    2019-10-28 pantxo Attached File- => Added task#14243_legend.patch, #47760
    2017-02-08 johasixt Carbon-Copy- => Added johasixt
    2016-11-30 reginalduck Carbon-Copy- => Added reginalduck
    2016-11-18 siko1056 Dependencies- => Depends on bugs #40333

    Back to the top


    Powered by Savane 3.5