Skip to content

ENH: plotting methods can unpack labeled data [MOVED TO #4829] #4787

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
wants to merge 8 commits into from
Closed
70 changes: 69 additions & 1 deletion lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
import warnings
import contextlib
import distutils.sysconfig

import functools
# cbook must import matplotlib only within function
# definitions, so it is safe to import from it here.
from matplotlib.cbook import is_string_like, mplDeprecation
Expand Down Expand Up @@ -1515,6 +1515,74 @@ def test(verbosity=1):

test.__test__ = False # nose: this function is not a test


def _replacer(data, key):
# if key isn't a string don't bother
if not isinstance(key, six.string_types):
return key
# try to use __getitem__
try:
return data[key]
# key does not exist, silently fall back to key
except KeyError:
return key


def unpack_labeled_data(wl_args=None, wl_kwargs=None, label_pos=None):
"""
A decorator to add a 'data' kwarg to any a function. The signature
of the input function must be ::

def foo(ax, *args, **kwargs)

so this is suitable for use with Axes methods.
"""
if label_pos is not None:
label_arg, label_kwarg = label_pos

if wl_kwargs is not None:
wl_kwargs = set(wl_kwargs)
if wl_args is not None:
wl_args = set(wl_args)

def param(func):
@functools.wraps(func)
def inner(ax, *args, **kwargs):
data = kwargs.pop('data', None)
if data is not None:
if wl_args is None:
new_args = tuple(_replacer(data, a) for a in args)
else:
new_args = tuple(_replacer(data, a) if j in wl_args else a
for j, a in enumerate(args))

if wl_kwargs is None:
new_kwargs = dict((k, _replacer(data, v))
for k, v in six.iteritems(kwargs))
else:
new_kwargs = dict(
(k, _replacer(data, v) if k in wl_kwargs else v)
for k, v in six.iteritems(kwargs))
else:
new_args, new_kwargs = args, kwargs

if (label_pos is not None and ('label' not in kwargs or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't check against the case that a user called the function with args alone (aka: the new label is in args). Not sure how frequent that is...

def plotter(ax, x, y, label=None):
    pass

plotter(None, 1,2,"label")

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, but I am not super worried about that in this case because in order for this code path to be hit they have to be using the data kwarg so we won't break un-touched user code.

Some of the methods take label as an explicit input, but many take it through a blind **kwargs. I suspect that we will have to inspect func on the way in.

kwargs['label'] is None)):
if len(args) > label_arg:
try:
kwargs['label'] = args[label_arg].name
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use @mwaskom's suggestion of using the text label instead of the .name attribute? That seems safer:

To be clear I'm not saying matplotlib needs to extract a .name attribute from a vector, just that if semantic names are used to draw the plot, they should end up as labels too.

.name will also work with xray, but the smaller we can make the labeled data spec, the better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should probably be changed to use either. I was trying to make this work for cases where the user is currently doing plt.plot(df['foo']) which while cutting against my long message is what I was thinking when I wrote this.

except AttributeError:
pass
elif label_kwarg in kwargs:
try:
new_kwargs['label'] = kwargs[label_kwarg].name
except AttributeError:
pass

return func(ax, *new_args, **new_kwargs)
return inner
return param

verbose.report('matplotlib version %s' % __version__)
verbose.report('verbose.level %s' % verbose.level)
verbose.report('interactive is %s' % is_interactive())
Expand Down
Loading