Skip to content
This repository was archived by the owner on Apr 5, 2019. It is now read-only.

Conversation

erikhuizinga
Copy link

@erikhuizinga erikhuizinga commented Jun 15, 2016

I've added some functionality to:

  • append:
    • The legend handle input argument is now optional. If not provided, the legend to be edited is chosen as the one in the current figure.
  • permute:
    • The legend handle input argument is now optional. If not provided, the legend to be edited is chosen as the one in the current figure.
    • The order input argument is now optional. If it is not provided, the order is permuted by shifting all legend entries down by one; the last entry is wrapped around to the first place.
  • remove:
    • The legend handle input argument is now optional. If not provided, the legend to be edited is chosen as the one in the current figure.

If multiple Legend objects are found in the current figure, the one closest to the current axes is chosen.

Some additional input parsing, error handling and warning text improvements were made.

Some code enhancements were made. I've added some functionality to: - append: - The legend handle input argument is now optional. If not provided, the legend to be edited is chosen as the one in the current figure. - permute: - The legend handle input argument is now optional. If not provided, the legend to be edited is chosen as the one in the current figure. - The order input argument is now optional. If it is not provided, the order is permuted by shifting all legend entries down by one; the last entry is wrapped around to the first place. - remove: - The legend handle input argument is now optional. If not provided, the legend to be edited is chosen as the one in the current figure. Some additional input parsing, error handling and warning text improvements were made.
Enhanced: - select now finds the legend belonging to the current axes via the respective PlotChildren and Children properties having the most matches. - remove now accepts zero input arguments: if none are provided, the last legend entry is removed. I've updated the help sections accordingly.
@sco1
Copy link
Member

sco1 commented Jun 16, 2016

Optional legend object inputs

Explicit is better than implicit. There are too many scenarios in MATLAB where gcf and gca will not return the desired object so using these to implicitly identify a legend object to work on is not something I feel comfortable including. It's far easier to explicitly pass the legend object than to handle the edge cases. Simple is better than complex.

See #3, where I explain the decision.

Optional permute order input

Again, explicit is better than implicit. Along with that, the behavior of legtools.permute is a mirror of MATLAB's permute built-in, where order is an explicit argument to the function. I do not want to break this mirror. The proposed functionality would be better suited for a circshift method, though a good case would need to be made for the usefulness of such a method in this context.

I will evaluate the remaining changes when I am able.

@sco1 sco1 self-assigned this Jun 16, 2016
@erikhuizinga
Copy link
Author

erikhuizinga commented Jun 17, 2016

I agree with your point that explicit is better than implicit. However, I've retained all explicit functionality, so there's no changes there. The value of not needing to specify the handle to the legend to be edited is its ease of use, e.g.:
figure
ezplot sin
legend sin
hold on
ezplot cos
legtools.append('cos')
legtools.permute

If you have to explicitly use the legend handle or specify order, the code becomes more cumbersome to write. For quick uses as in my example the implicit functionality works just fine.

Besides, when do gcf and gca not return the current figure or axes consequently? I've updated the help sections to reflect this: the current figure and current axes are mentioned as the source of the legend to be edited. It's up to the user of legtools to make sure what the current figure and axes are.

@erikhuizinga
Copy link
Author

erikhuizinga commented Jun 17, 2016

Ideas for further enhancement of legtools: new methods:

  • update: updates the Legend's properties through Name,Value pairs that are accepted by MATLAB's built-in legend function, without the need of respecifying the strings of the Legend entries. This is useful, e.g. to relocate a legend (e.g. 'location','best') or to update any other property.
  • calling legtools will return the handle to the legend in the current axes in the current figure. This is easier than calling lh = findobj(gcf,'type','legend') and then having to find the correct handle if more than one exists. It seems nothing but logical to me that a class like legtools would incorporate such a function. Optionally a handle to axes can be specified to obtain the handle to its legend, if it has one.
@sco1
Copy link
Member

sco1 commented Jun 17, 2016

If you have to explicitly use the legend handle or specify order, the code becomes more cumbersome to write.

The explicit equivalent to your example is the following:

figure ezplot sin lh = legend('sin'); hold on ezplot cos legtools.append(lh, 'cos') 

This isn't cumbersome.

As I already stated, removing the explicit order requirement from permute creates behavior that diverges from that of MATLAB's permute and is not desired. I also don't see any practical purpose for circshifting legend entries by one other than "just because," which isn't really a good enough reason to include something in a piece of code.

Besides, when do gcf and gca not return the current figure or axes consequently?

MATLAB's documentation already explains this.

gca:

The current axes is the target for graphics output. It is the axes in which graphics commands such as plot, text, and surf draw their results. It is typically the last axes created or the last axes clicked with the mouse. Changing the current figure also changes the current axes.
User interaction can change the current axes. If you need to access a specific axes, store the axes handle in your program code when you create the axes and use this handle instead of gca.

gcf:

The current figure is the target for graphics output. It is the figure window in which graphics commands such as plot, title, and surf draw their results. It is typically the last figure created or the last figure clicked with the mouse.
User interaction can change the current figure. If you need to access a specific figure, store the figure handle in your program code when you create the figure and use this handle instead of gcf.

The user working with (read: clicking on) multiple figure windows, subplots, GUIs, etc. introduces uncertainty in the gca/gcf values. MATLAB's advice for this is to store the object handle and pass it explicitly. I agree.

It's up to the user of legtools to make sure what the current figure and axes are.

That's silly. Relying on the user to ensure that gca/gcf points to the appropriate object introduces a totally unnecessary failure mode. Passing the object to operate on is MATLAB's explicit recommendation and is behavior in line with MATLAB's set functionality.

For quick uses as in my example the implicit functionality works just fine.

I don't disagree that it works fine for simple, linear cases like the above example. However, designing code around quick uses generally leads to less robust code and/or spaghetti code as you have to tweak things to correct for unexpected behavior. You're free to maintain your own fork of this project that is tailored to your specific wants and needs. That's the beauty of open source.

@sco1
Copy link
Member

sco1 commented Jun 17, 2016

To the further enhancements: Both good ideas but they're already achievable with MATLAB's built-ins.

MATLAB's legend will return the current axes' legend object:

plot(1:10) legend('hi') lh = legend; 

Which returns:

lh = Legend (hi) with properties: String: {'hi'} Location: 'northeast' Orientation: 'vertical' FontSize: 9 Position: [0.7905 0.8468 0.0911 0.0476] Units: 'normalized' 

You can use lh to modify the object as desired, which obviates the need for the update method.

@erikhuizinga
Copy link
Author

Thanks for your elaborate answer. I agree with you on many points. It's
probably my own knowledge about MATLAB's behaviour when the user interacts
with figures/axes that caused me to think implicit legend handle selection
was a nice feature. It is indeed not useful when your code must be certain
about editing a legend/axes/figure.

About legtools.permute and MATLAB's built-in permute: the built-in
version permutes dimensions of an array (e.g. transpose), it does not
permute elements; thus the comparison you make is not entirely fair, but I
agree that the current legtools.permute reflects the built-in function in
that it explicitly needs the order argument. The cirshift function
needs at least two inputs too.

And thanks for reminding me about lh = legend;, this is much easier than
my code. It appears to return the legend in the current axes, which is not
mentioned in the documentation. You can also use lh = legend(ax); with
ax the handle to the axes you want to obtain the legend from; lh is
empty when ax contains no legend. I've found a bug too: if ax is an
array of axes handles, MATLAB R2014b returns an error and one of the
legends is deleted (or becomes invisible). I was hoping it would return the
array of legends in the specified axes. E.g. try:

figure, ax(1) = subplot(2,1,1); ezplot sin, legend sin, ax(2) = subplot(2,1,2); ezplot cos, legend cos, lh = legend(ax), which errors and
removes the sin legend entry.

Thanks for your offer on continuing my own fork of legtools. What do you
suggest for a name if I want to publish it on the MATLAB File Exchange? I
could use ‘legtools’ or ‘legtools enhanced’, but both names are kind of
unfair to the current legtools, don't you think?

2016-06-17 14:42 GMT+02:00 sco1 notifications@github.com:

To the further enhancements: Both good ideas but they're already
achievable with MATLAB's built-ins.

MATLAB's legend http://www.mathworks.com/help/matlab/ref/legend.html
will return the current axes' legend object:

figure
plot(1:10)
legend('hi')
lh = legend;

Which returns:

lh =

Legend (hi) with properties:

 String: {'hi'} Location: 'northeast' Orientation: 'vertical' FontSize: 9 Position: [0.7905 0.8468 0.0911 0.0476] Units: 'normalized' 

You can use lh to modify the object as desired, which obviates the need
for the update method.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#4 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/ASeikF_N_T_QJDOQWHaNf1ppQ6FIwLVBks5qMpY7gaJpZM4I2pDL
.

@sco1
Copy link
Member

sco1 commented Jun 17, 2016

the built-in version permutes dimensions of an array (e.g. transpose), it does not permute elements; thus the comparison you make is not entirely fair

For the 1D case rearranging the dimensions of the array is functionally equivalent to the dictionary definition of permute.

I've found a bug too: if ax is an array of axes handles, MATLAB R2014b returns an error and one of the legends is deleted (or becomes invisible).

This isn't a bug, it's by design.

Because legend only supports operating on one axes object, in all cases of calling legend it first attempts to locate a single axes object to work on. If no inputs are specified, it uses the current axes. If inputs are passed and the first is one or more axes objects, only the first one is used.

Calling legend with no inputs short-circuits the function and goes directly to setting the outputs. When you call legend with one input it's treated as though you are using command syntax and uses a switch/case block to compare the input to the 'standard' options (e.g. 'boxon', 'toggle', etc.). If none of these are matched, the otherwise statement of this block calls the make_legend function. Calling legend with more than one input argument goes directly to the make_legend function.

The first thing make_legend does is delete any existing legends in the axes object. It then checks the first input to see if it's an object handle. The assumption here is that the user is passing an array of graphics handles to be included in the legend. Being an object does not guarantee it's valid to place in the legend, so another check is made to see if the object is legendable. Because an axes object is not legendable, the error is thrown. It's a pretty callous check that could probably be made better (ignore non-legendable objects and warn the user), but the list of things that could be made better in MATLAB is long. It's also not the best error message, though in R2016a at least there is a TODO to expand the error message catalog.

Thanks for your offer on continuing my own fork of legtools. What do you suggest for a name if I want to publish it on the MATLAB File Exchange? I could use ‘legtools’ or ‘legtools enhanced’, but both names are kind of unfair to the current legtools, don't you think?

It doesn't really matter to me as long as proper attribution is used. I also don't want to convey that I'm not open to including changes in the code I'm maintaining here. I am open to it and I encourage it.

@sco1
Copy link
Member

sco1 commented Jun 20, 2016

With the vast majority of the changes in this PR being tied to the gcf/gca functionality I'm going to close this for the reasons already discussed.

@sco1 sco1 closed this Jun 20, 2016
@erikhuizinga erikhuizinga deleted the patch-1 branch October 1, 2016 16:02
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

2 participants