Skip to content

OrderedDict legends no longer work 2.1 #10262

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jakereps opened this issue Jan 16, 2018 · 10 comments
Closed

OrderedDict legends no longer work 2.1 #10262

jakereps opened this issue Jan 16, 2018 · 10 comments
Milestone

Comments

@jakereps
Copy link

jakereps commented Jan 16, 2018

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:
@jklymak
Copy link
Member

jklymak commented Jan 16, 2018

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

@QuLogic QuLogic added this to the v2.1.2 milestone Jan 16, 2018
@jklymak
Copy link
Member

jklymak commented Jan 16, 2018

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

@jklymak jklymak closed this as completed Jan 16, 2018
@jakereps
Copy link
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

@anntzer
Copy link
Contributor

anntzer commented Jan 16, 2018

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

@jklymak
Copy link
Member

jklymak commented Jan 16, 2018

@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.

@jklymak jklymak reopened this Jan 16, 2018
@jklymak jklymak changed the title Retrieving legend handles and labels no longer works in 2.1.1 OrderedDict legends no longer work 2.1 Jan 16, 2018
@jakereps
Copy link
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))

@jklymak
Copy link
Member

jklymak commented Jan 16, 2018

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

@jakereps
Copy link
Author

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

@jakereps
Copy link
Author

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

image

@dstansby
Copy link
Member

Fixed via. #10263

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants