Skip to content

OrderedDict legends no longer work 2.1 #10262

Closed

Description

@jakereps

Bug report

Legend handles and labels retrieval break as of 2.1.1. Trying to call ax.get_legend_handles_labels() results in a comparison ValueError being thrown. git bisect led to 4f594f4 being the start of the issue.

Code for reproduction

%matplotlib inline
import collections
import matplotlib.pyplot as plt
import numpy as np

X = np.random.randn(10)
Y = np.random.randn(10)
labels = ['a'] * 5 + ['b'] * 5
colors = ['r'] * 5 + ['g'] * 5

fig, ax = plt.subplots()
for x, y, label, color in zip(X, Y, labels, colors):
    ax.scatter(x, y, label=label, c=color)

handles, labels = ax.get_legend_handles_labels()

Actual outcome

...in _in_handles(h, l)
   1342                 pass
   1343             try:
-> 1344                 if f_h.get_facecolor() != h.get_facecolor():
   1345                     continue
   1346             except AttributeError:

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Expected outcome

Produce a scatter plot, as expected. This worked in 2.1.0.

Matplotlib version

  • Operating system: Ubuntu 16.04 and macOS 10.13
  • Matplotlib version: 2.1.1
  • Matplotlib backend (print(matplotlib.get_backend())): agg on both
  • Python version: 3.5.4
  • Jupyter version (if applicable): 4.4.0
  • Other libraries:

Activity

  1. jklymak commented on Jan 16, 2018

    @jklymak
    Member

    Sorry this broke for you. Can you make a non-pandas example and I’ll try and look at what went wrong.

  2. added this to the v2.1.2 milestone on Jan 16, 2018
  3. jklymak commented on Jan 16, 2018

    @jklymak
    Member

    oops, sorry, this is a dupe and already fixed in master, and will soon be released on 2.1.2... See #10064

  4. jakereps commented on Jan 16, 2018

    @jakereps
    Author

    Thanks @jklymak, it seems that my first issue is resolved by the current HEAD, but a new issue remains through using the collections.OrderedDict legend "gathering" method. Not sure if code changed internally to not support taking ordered dict values/keys items or if a cast to list or something was removed.

    Curious if this is a bug or not, as the collections.OrderedDict way is (anecdotally) the go-to method for getting the handles/labels correctly grouped for customizing a legend.

    Same exact code as my first issue, but the final step of:

    ...
    ax.legend(legend.values(), legend.keys(), loc=6, bbox_to_anchor=(1, 0.5))

    Which produces the following error output:

    ~/Developer/.../.../.../report_functions.py in princincipal_coordinates(samples, data, features, category)
         85                     by_label.keys(),
         86                     bbox_to_anchor=(1, 1.5),
    ---> 87                     loc=6)
         88 
         89     text = 'Samples were rarefied to {} retaining {} of {} samples'
    
    ~/Developer/mc3/envs/.../lib/python3.5/site-packages/matplotlib/axes/_axes.py in legend(self, *args, **kwargs)
        554         if len(extra_args):
        555             raise TypeError('legend only accepts two non-keyword arguments')
    --> 556         self.legend_ = mlegend.Legend(self, handles, labels, **kwargs)
        557         self.legend_._remove_method = lambda h: setattr(self, 'legend_', None)
        558         return self.legend_
    
    ~/Developer/mc3/envs/.../lib/python3.5/site-packages/matplotlib/legend.py in __init__(self, parent, handles, labels, loc, numpoints, markerscale, markerfirst, scatterpoints, scatteryoffsets, prop, fontsize, borderpad, labelspacing, handlelength, handleheight, handletextpad, borderaxespad, columnspacing, ncol, mode, fancybox, shadow, title, framealpha, edgecolor, facecolor, bbox_to_anchor, bbox_transform, frameon, handler_map)
        590         del locals_view
        591         # trim handles and labels if illegal label...
    --> 592         for label, handle in zip(labels[:], handles[:]):
        593                 if (isinstance(label, six.string_types) and
        594                         label.startswith('_')):
    
    TypeError: 'odict_keys' object is not subscriptable
  5. anntzer commented on Jan 16, 2018

    @anntzer
    Contributor

    Looks like we need something like

    diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py
    index 9ce249a13..79b3846e3 100644
    --- a/lib/matplotlib/legend.py
    +++ b/lib/matplotlib/legend.py
    @@ -589,16 +589,18 @@ class Legend(Artist):
                 setattr(self, name, value)
             del locals_view
             # trim handles and labels if illegal label...
    -        for label, handle in zip(labels[:], handles[:]):
    -                if (isinstance(label, six.string_types) and
    -                        label.startswith('_')):
    -                    warnings.warn('The handle {!r} has a label of {!r} which '
    -                                  'cannot be automatically added to the '
    -                                  'legend.'.format(handle, label))
    -                    labels.remove(label)
    -                    handles.remove(handle)
    +        _cleaned_labels = []
    +        _cleaned_handles = []
    +        for label, handle in zip(labels, handles):
    +            if isinstance(label, six.string_types) and label.startswith('_'):
    +                warnings.warn('The handle {!r} has a label of {!r} which '
    +                              'cannot be automatically added to the legend.'
    +                              .format(handle, label))
    +            else:
    +                _cleaned_labels.append(label)
    +                _cleaned_handles.append(handle)
    +        labels, handles = _cleaned_labels, _cleaned_handles
     
    -        handles = list(handles)
             if len(handles) < 2:
                 ncol = 1
             self._ncol = ncol
    
  6. jklymak commented on Jan 16, 2018

    @jklymak
    Member

    @jakereps I can guess what you are doing, but can you make it explicit? legends is not defined yet in your code snippet.

    Again, it'd be clearer w/o pandas... No doubt @anntzer is correct, that we need to get rid of the [:], but it'd be nice to have a complete example to test.

  7. changed the title [-]Retrieving legend handles and labels no longer works in 2.1.1[/-] [+]OrderedDict legends no longer work 2.1[/+] on Jan 16, 2018
  8. jakereps commented on Jan 16, 2018

    @jakereps
    Author

    Oh true, sorry about that, I copied from further into the real codebase, but forgot I didn't include the legend variable creation in the example.

    Here's the example sans pandas:

    import collections
    import matplotlib.pyplot as plt
    import numpy as np
    
    X = np.random.randn(10)
    Y = np.random.randn(10)
    labels = ['a'] * 5 + ['b'] * 5
    colors = ['r'] * 5 + ['g'] * 5
    
    fig, ax = plt.subplots()
    for x, y, label, color in zip(X, Y, labels, colors):
        ax.scatter(x, y, label=label, c=color)
    
    handles, labels = ax.get_legend_handles_labels()
    legend = collections.OrderedDict(zip(labels, handles))
    ax.legend(legend.values(), legend.keys(), loc=6, bbox_to_anchor=(1, .5))
  9. jklymak commented on Jan 16, 2018

    @jklymak
    Member

    Thanks @jakereps if you are able to compare my PR that'd be great, but it works w/ the test above....

  10. jakereps commented on Jan 16, 2018

    @jakereps
    Author

    Will do! Currently installing the PR (dropped back to 2.1.0 as a stopgap, so rebuilding the dev install).

  11. jakereps commented on Jan 16, 2018

    @jakereps
    Author

    Perfect! Looks good to me! Thanks @jklymak and @anntzer!

    image

  12. dstansby commented on Jan 18, 2018

    @dstansby
    Member

    Fixed via. #10263

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions