Skip to content

Commit 4c27753

Browse files
authored
Merge pull request #153 from lukelbd/auto-legend-fix
Auto legend fix
2 parents d3e1e78 + 2a2f2d2 commit 4c27753

File tree

1 file changed

+57
-38
lines changed

1 file changed

+57
-38
lines changed

proplot/wrappers.py

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ def default_crs(self, func, *args, crs=None, **kwargs):
206206
except TypeError as err: # duplicate keyword args, i.e. crs is positional
207207
if not args:
208208
raise err
209-
result = func(self, *args[:-1], crs=args[-1], **kwargs)
209+
args, crs = args[:-1], args[-1]
210+
result = func(self, *args, crs=crs, **kwargs)
210211
# Fix extent, so axes tight bounding box gets correct box!
211212
# From this issue:
212213
# https://github.com/SciTools/cartopy/issues/1207#issuecomment-439975083
@@ -217,15 +218,18 @@ def default_crs(self, func, *args, crs=None, **kwargs):
217218
return result
218219

219220

220-
def _standard_label(data, axis=None, units=True):
221+
def _axis_labels_title(data, axis=None, units=True):
221222
"""
222-
Get data and label for pandas or xarray objects or their coordinates.
223+
Get data and label for pandas or xarray objects or their coordinates
224+
along axis `axis`. If `units` is ``True`` also look for units on xarray
225+
data arrays.
223226
"""
224227
label = ''
225228
_load_objects()
226229
if isinstance(data, ndarray):
227230
if axis is not None and data.ndim > axis:
228231
data = np.arange(data.shape[axis])
232+
229233
# Xarray with common NetCDF attribute names
230234
elif isinstance(data, DataArray):
231235
if axis is not None and data.ndim > axis:
@@ -239,6 +243,7 @@ def _standard_label(data, axis=None, units=True):
239243
label = f'{label} ({units})'
240244
elif units:
241245
label = units
246+
242247
# Pandas object with name attribute
243248
# if not label and isinstance(data, DataFrame) and data.columns.size == 1:
244249
elif isinstance(data, (DataFrame, Series, Index)):
@@ -251,6 +256,7 @@ def _standard_label(data, axis=None, units=True):
251256
# DataFrame has no native name attribute but user can add one:
252257
# https://github.com/pandas-dev/pandas/issues/447
253258
label = getattr(data, 'name', '') or ''
259+
254260
return data, str(label).strip()
255261

256262

@@ -307,7 +313,7 @@ def standardize_1d(self, func, *args, **kwargs):
307313
if x is None:
308314
axis = 1 if (name in ('hist', 'boxplot', 'violinplot') or any(
309315
kwargs.get(s, None) for s in ('means', 'medians'))) else 0
310-
x, _ = _standard_label(y, axis=axis)
316+
x, _ = _axis_labels_title(y, axis=axis)
311317
x = _to_array(x)
312318
if x.ndim != 1:
313319
raise ValueError(
@@ -332,20 +338,21 @@ def standardize_1d(self, func, *args, **kwargs):
332338
kwargs['positions'] = xi
333339
if name in ('boxplot', 'violinplot'):
334340
kwargs['positions'] = xi
341+
335342
# Next handle labels if 'autoformat' is on
336343
if self.figure._auto_format:
337344
# Ylabel
338-
y, label = _standard_label(y)
339-
if label:
340-
# for histogram, this indicates x coordinate
345+
y, label = _axis_labels_title(y)
346+
if label: # for histogram, this label is used for *x* coordinates
341347
iaxis = xax if name in ('hist',) else yax
342348
kw[iaxis + 'label'] = label
343349
# Xlabel
344-
x, label = _standard_label(x)
350+
x, label = _axis_labels_title(x)
345351
if label and name not in ('hist',):
346352
kw[xax + 'label'] = label
347353
if name != 'scatter' and len(x) > 1 and xi is None and x[1] < x[0]:
348354
kw[xax + 'reverse'] = True
355+
349356
# Appply
350357
if kw:
351358
self.format(**kw)
@@ -550,7 +557,7 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
550557
# Handle labels if 'autoformat' is on
551558
if self.figure._auto_format:
552559
for key, xy in zip(('xlabel', 'ylabel'), (x, y)):
553-
_, label = _standard_label(xy)
560+
_, label = _axis_labels_title(xy)
554561
if label:
555562
kw[key] = label
556563
if len(xy) > 1 and all(isinstance(xy, Number)
@@ -562,8 +569,8 @@ def standardize_2d(self, func, *args, order='C', globe=False, **kwargs):
562569
y = yi
563570
# Handle figure titles
564571
if self.figure._auto_format:
565-
_, colorbar_label = _standard_label(Zs[0], units=True)
566-
_, title = _standard_label(Zs[0], units=False)
572+
_, colorbar_label = _axis_labels_title(Zs[0], units=True)
573+
_, title = _axis_labels_title(Zs[0], units=False)
567574
if title:
568575
kw['title'] = title
569576
if kw:
@@ -1789,7 +1796,7 @@ def cycle_changer(
17891796

17901797
# Plot susccessive columns
17911798
objs = []
1792-
label_leg = None # for colorbar or legend
1799+
label_leg_cbar = None # for colorbar or legend
17931800
for i in range(ncols):
17941801
# Prop cycle properties
17951802
kw = kwargs.copy()
@@ -1806,59 +1813,70 @@ def cycle_changer(
18061813
kw[key] = value
18071814

18081815
# Get x coordinates
1809-
ix, iy = x, ys[0] # samples
1816+
x_col, y_first = x, ys[0] # samples
18101817
if name in ('pie',):
1811-
kw['labels'] = _not_none(labels, ix) # TODO: move to pie wrapper?
1818+
kw['labels'] = _not_none(labels, x_col) # TODO: move to pie wrapper?
18121819
if name in ('bar',): # adjust
18131820
if not stacked:
1814-
ix = x + (i - ncols / 2 + 0.5) * width / ncols
1815-
elif stacked and iy.ndim > 1:
1821+
x_col = x + (i - ncols / 2 + 0.5) * width / ncols
1822+
elif stacked and y_first.ndim > 1:
18161823
key = 'x' if barh else 'bottom'
1817-
kw[key] = _to_indexer(iy)[:, :i].sum(axis=1)
1824+
kw[key] = _to_indexer(y_first)[:, :i].sum(axis=1)
18181825

18191826
# Get y coordinates and labels
18201827
if name in ('pie', 'boxplot', 'violinplot'):
1821-
iys = (iy,) # only ever have one y value, cannot have legend labs
1828+
# Only ever have one y value, cannot have legend labs
1829+
ys_col = (y_first,)
1830+
18221831
else:
18231832
# The coordinates
18241833
# WARNING: If stacked=True then we always *ignore* second
18251834
# argument passed to fill_between. Warning should be issued
18261835
# by fill_between_wrapper in this case.
18271836
if stacked and 'fill_between' in name:
1828-
iys = tuple(
1829-
iy if iy.ndim == 1 else _to_indexer(iy)[:, :ii].sum(axis=1)
1837+
ys_col = tuple(
1838+
y_first if y_first.ndim == 1
1839+
else _to_indexer(y_first)[:, :ii].sum(axis=1)
18301840
for ii in (i, i + 1)
18311841
)
18321842
else:
1833-
iys = tuple(
1834-
iy if iy.ndim == 1 else _to_indexer(iy)[:, i]
1835-
for iy in ys
1843+
ys_col = tuple(
1844+
y_i if y_i.ndim == 1 else _to_indexer(y_i)[:, i]
1845+
for y_i in ys
18361846
)
1847+
18371848
# Possible legend labels
1849+
# Several scenarios:
1850+
# 1. Always prefer input labels
1851+
# 2. Always add labels if this is a *named* dimension.
1852+
# 3. Even if not *named* dimension add labels if labels are string
18381853
if len(labels) != ncols:
18391854
raise ValueError(
18401855
f'Got {ncols} columns in data array, '
18411856
f'but {len(labels)} labels.'
18421857
)
1843-
label = labels[i]
1844-
values, label_leg = _standard_label(iy, axis=1)
1845-
if label_leg and label is None:
1846-
label = _to_ndarray(values)[i]
1858+
label = labels[i] # input labels
1859+
labels_cols, label_leg_cbar = _axis_labels_title(y_first, axis=1)
1860+
labels_cols = _to_ndarray(labels_cols)
1861+
if label is None and (
1862+
label_leg_cbar or labels_cols.size and isinstance(labels_cols[i], str)
1863+
):
1864+
label = labels_cols[i]
18471865
if label is not None:
18481866
kw['label'] = label
18491867

18501868
# Build coordinate arguments
1851-
xy = ()
1869+
x_ys_col = ()
18521870
if barh: # special, use kwargs only!
1853-
kw.update({'bottom': ix, 'width': iys[0]})
1871+
kw.update({'bottom': x_col, 'width': ys_col[0]})
18541872
kw.setdefault('x', kwargs.get('bottom', 0)) # required
18551873
elif name in ('pie', 'hist', 'boxplot', 'violinplot'):
1856-
xy = (*iys,)
1874+
x_ys_col = ys_col
18571875
else: # has x-coordinates, and maybe more than one y
1858-
xy = (ix, *iys)
1876+
x_ys_col = (x_col, *ys_col)
18591877

18601878
# Call plotting function
1861-
obj = func(self, *xy, *args, **kw)
1879+
obj = func(self, *x_ys_col, *args, **kw)
18621880
if isinstance(obj, (list, tuple)) and len(obj) == 1:
18631881
obj = obj[0]
18641882
objs.append(obj)
@@ -1873,8 +1891,8 @@ def cycle_changer(
18731891
# Add keywords
18741892
if loc != 'fill':
18751893
colorbar_kw.setdefault('loc', loc)
1876-
if label_leg:
1877-
colorbar_kw.setdefault('label', label_leg)
1894+
if label_leg_cbar:
1895+
colorbar_kw.setdefault('label', label_leg_cbar)
18781896
self._auto_colorbar[loc][1].update(colorbar_kw)
18791897

18801898
# Add legend
@@ -1887,8 +1905,8 @@ def cycle_changer(
18871905
# Add keywords
18881906
if loc != 'fill':
18891907
legend_kw.setdefault('loc', loc)
1890-
if label_leg:
1891-
legend_kw.setdefault('label', label_leg)
1908+
if label_leg_cbar:
1909+
legend_kw.setdefault('label', label_leg_cbar)
18921910
self._auto_legend[loc][1].update(legend_kw)
18931911

18941912
# Return
@@ -2270,6 +2288,7 @@ def cmap_changer(
22702288
colorbar_kw = colorbar_kw or {}
22712289

22722290
# Flexible user input
2291+
Z_sample = args[-1]
22732292
vmin = _not_none(vmin=vmin, norm_kw_vmin=norm_kw.pop('vmin', None))
22742293
vmax = _not_none(vmax=vmax, norm_kw_vmax=norm_kw.pop('vmax', None))
22752294
values = _not_none(values=values, centers=centers)
@@ -2349,7 +2368,7 @@ def cmap_changer(
23492368
ticks = None
23502369
if cmap is not None and name not in ('hexbin',):
23512370
norm, cmap, levels, ticks = _build_discrete_norm(
2352-
args[-1], # sample data for getting suitable levels
2371+
Z_sample, # sample data for getting suitable levels
23532372
levels=levels, values=values,
23542373
norm=norm, norm_kw=norm_kw,
23552374
locator=locator, locator_kw=locator_kw,
@@ -2471,7 +2490,7 @@ def cmap_changer(
24712490
if colorbar:
24722491
loc = self._loc_translate(colorbar, 'colorbar', allow_manual=False)
24732492
if 'label' not in colorbar_kw and self.figure._auto_format:
2474-
_, label = _standard_label(args[-1]) # last one is data, we assume
2493+
_, label = _axis_labels_title(Z_sample) # last one is data, we assume
24752494
if label:
24762495
colorbar_kw.setdefault('label', label)
24772496
if name in ('parametric',) and values is not None:

0 commit comments

Comments
 (0)