Skip to content

Commit 6ae1742

Browse files
committed
Restore+expand mpl behavior by supporting successive legend additions
1 parent c19e637 commit 6ae1742

File tree

2 files changed

+161
-157
lines changed

2 files changed

+161
-157
lines changed

proplot/axes/base.py

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,8 @@ def __init__(self, *args, number=None, main=False, _subplotspec=None, **kwargs):
251251
self.figure._axes_main.append(self)
252252

253253
# On-the-fly legends and colorbars
254-
self._auto_colorbar = {}
255-
self._auto_legend = {}
254+
self._queued_colorbars = {}
255+
self._queued_legends = {}
256256

257257
# Figure row and column labels
258258
# NOTE: Most of these sit empty for most subplots
@@ -370,18 +370,20 @@ def shared(paxs):
370370
for child in children:
371371
child._sharey_setup(parent)
372372

373-
def _draw_auto_legends_colorbars(self):
373+
def _draw_queued_colorbars_legends(self):
374374
"""
375375
Generate automatic legends and colorbars. Wrapper funcs
376376
let user add handles to location lists with successive calls to
377377
make successive calls to plotting commands.
378378
"""
379-
for loc, (handles, kwargs) in self._auto_colorbar.items():
379+
for loc, (handles, kwargs) in self._queued_colorbars.items():
380+
kwargs.setdefault('loc', loc)
380381
self.colorbar(handles, **kwargs)
381-
for loc, (handles, kwargs) in self._auto_legend.items():
382-
self.legend(handles, **kwargs)
383-
self._auto_legend = {}
384-
self._auto_colorbar = {}
382+
for loc, (handles, kwargs) in self._queued_legends.items():
383+
kwargs.setdefault('loc', loc)
384+
self.legend(handles, queue=False, **kwargs)
385+
self._queued_legends = {}
386+
self._queued_colorbars = {}
385387

386388
def _get_extent_axes(self, x, panels=False):
387389
"""
@@ -468,7 +470,7 @@ def _is_panel_group_member(self, other):
468470
or other._panel_parent is self._panel_parent # sibling
469471
)
470472

471-
def _loc_translate(self, loc, mode=None, allow_manual=True):
473+
def _loc_translate(self, loc, mode=None):
472474
"""
473475
Return the location string `loc` translated into a standardized form.
474476
"""
@@ -510,13 +512,12 @@ def _loc_translate(self, loc, mode=None, allow_manual=True):
510512
+ ', '.join(map(repr, loc_translate)) + '.'
511513
)
512514
elif (
513-
allow_manual
514-
and mode == 'legend'
515+
mode == 'legend'
515516
and np.iterable(loc)
516517
and len(loc) == 2
517518
and all(isinstance(l, Number) for l in loc)
518519
):
519-
loc = np.array(loc)
520+
loc = tuple(loc)
520521
else:
521522
raise KeyError(f'Invalid {mode} location {loc!r}.')
522523
if mode == 'colorbar' and loc == 'best': # white lie
@@ -535,6 +536,30 @@ def inset_locator(ax, renderer):
535536
return bb
536537
return inset_locator
537538

539+
def _queue_driver(self, legend, loc, *args, **kwargs):
540+
"""
541+
Driver function.
542+
"""
543+
database = self._queued_legends if legend else self._queued_colorbars
544+
database.setdefault(loc, ([], {}))
545+
database[loc][0].extend(args)
546+
if legend and 'labels' in kwargs:
547+
database[loc][1].setdefault('labels', [])
548+
database[loc][1]['labels'].extend(kwargs.pop('labels'))
549+
database[loc][1].update(kwargs)
550+
551+
def _queue_colorbar(self, *args, **kwargs):
552+
"""
553+
Queue up objects for list-of-artist style colorbars.
554+
"""
555+
self._queue_driver(False, *args, **kwargs)
556+
557+
def _queue_legend(self, *args, **kwargs):
558+
"""
559+
Queues up objects for legends.
560+
"""
561+
self._queue_driver(True, *args, **kwargs)
562+
538563
def _range_gridspec(self, x):
539564
"""
540565
Return the column or row gridspec range for the axes.
@@ -1233,18 +1258,12 @@ def colorbar(
12331258

12341259
# Inset colorbar
12351260
else:
1236-
# Default props
1261+
# Default properties
12371262
cbwidth, cblength = width, length
12381263
width, height = self.get_size_inches()
1239-
extend = units(_not_none(
1240-
kwargs.get('extendsize', None), rc['colorbar.insetextend']
1241-
))
1242-
cbwidth = units(_not_none(
1243-
cbwidth, rc['colorbar.insetwidth']
1244-
)) / height
1245-
cblength = units(_not_none(
1246-
cblength, rc['colorbar.insetlength']
1247-
)) / width
1264+
cbwidth = units(_not_none(cbwidth, rc['colorbar.insetwidth'])) / height
1265+
cblength = units(_not_none(cblength, rc['colorbar.insetlength'])) / width
1266+
extend = units(_not_none(kwargs.get('extendsize', None), rc['colorbar.insetextend'])) # noqa: E501
12481267
pad = units(_not_none(pad, rc['colorbar.insetpad']))
12491268
xpad, ypad = pad / width, pad / height
12501269

@@ -1257,21 +1276,18 @@ def colorbar(
12571276
xspace += 1.2 * rc['font.size'] / 72
12581277
xspace /= height # space for labels
12591278
if loc == 'upper right':
1260-
bounds = (1 - xpad - cblength, 1 - ypad - cbwidth)
1261-
fbounds = (
1262-
1 - 2 * xpad - cblength,
1263-
1 - 2 * ypad - cbwidth - xspace
1264-
)
1279+
ibounds = (1 - xpad - cblength, 1 - ypad - cbwidth)
1280+
fbounds = (1 - 2 * xpad - cblength, 1 - 2 * ypad - cbwidth - xspace)
12651281
elif loc == 'upper left':
1266-
bounds = (xpad, 1 - ypad - cbwidth)
1282+
ibounds = (xpad, 1 - ypad - cbwidth)
12671283
fbounds = (0, 1 - 2 * ypad - cbwidth - xspace)
12681284
elif loc == 'lower left':
1269-
bounds = (xpad, ypad + xspace)
1285+
ibounds = (xpad, ypad + xspace)
12701286
fbounds = (0, 0)
12711287
else:
1272-
bounds = (1 - xpad - cblength, ypad + xspace)
1288+
ibounds = (1 - xpad - cblength, ypad + xspace)
12731289
fbounds = (1 - 2 * xpad - cblength, 0)
1274-
bounds = (bounds[0], bounds[1], cblength, cbwidth)
1290+
ibounds = (ibounds[0], ibounds[1], cblength, cbwidth)
12751291
fbounds = (
12761292
fbounds[0], fbounds[1],
12771293
2 * xpad + cblength, 2 * ypad + cbwidth + xspace
@@ -1280,9 +1296,7 @@ def colorbar(
12801296
# Make frame
12811297
# NOTE: We do not allow shadow effects or fancy edges effect.
12821298
# Also keep zorder same as with legend.
1283-
frameon = _not_none(
1284-
frame=frame, frameon=frameon, default=rc['colorbar.frameon'],
1285-
)
1299+
frameon = _not_none(frame=frame, frameon=frameon, default=rc['colorbar.frameon']) # noqa: E501
12861300
if frameon:
12871301
xmin, ymin, width, height = fbounds
12881302
patch = mpatches.Rectangle(
@@ -1303,7 +1317,7 @@ def colorbar(
13031317

13041318
# Make axes
13051319
from .cartesian import CartesianAxes
1306-
locator = self._make_inset_locator(bounds, self.transAxes)
1320+
locator = self._make_inset_locator(ibounds, self.transAxes)
13071321
bbox = locator(None, None)
13081322
ax = CartesianAxes(self.figure, bbox.bounds, zorder=5)
13091323
ax.set_axes_locator(locator)
@@ -1322,16 +1336,14 @@ def colorbar(
13221336
warnings._warn_proplot(
13231337
'Inset colorbars can only have ticks on the bottom.'
13241338
)
1325-
kwargs.update({
1326-
'orientation': 'horizontal', 'ticklocation': 'bottom'
1327-
})
1339+
kwargs.update({'orientation': 'horizontal', 'ticklocation': 'bottom'})
13281340
kwargs.setdefault('maxn', 5)
13291341
kwargs.setdefault('extendsize', extend)
13301342

13311343
# Generate colorbar
13321344
return colorbar_wrapper(ax, *args, **kwargs)
13331345

1334-
def legend(self, *args, loc=None, width=None, space=None, **kwargs):
1346+
def legend(self, *args, loc=None, width=None, space=None, queue=True, **kwargs):
13351347
"""
13361348
Add an *inset* legend or *outer* legend along the edge of the axes.
13371349
See `~proplot.axes.legend_wrapper` for details.
@@ -1372,6 +1384,10 @@ def legend(self, *args, loc=None, width=None, space=None, **kwargs):
13721384
box. Units are interpreted by `~proplot.utils.units`.
13731385
When :rcraw:`tight` is ``True``, this is adjusted automatically.
13741386
Otherwise, the default is :rc:`subplots.panelpad`.
1387+
queue : bool, optional
1388+
Whether to queue the legend or draw it immediately. Default is ``True``.
1389+
This lets you successively add items to the legend in location `loc`
1390+
by calling e.g. `ax.legend(h, loc=loc)` more than once.
13751391
13761392
Other parameters
13771393
----------------
@@ -1415,8 +1431,21 @@ def legend(self, *args, loc=None, width=None, space=None, **kwargs):
14151431
else:
14161432
raise ValueError(f'Invalid panel side {side!r}.')
14171433

1418-
# Draw legend
1419-
return legend_wrapper(self, *args, loc=loc, **kwargs)
1434+
# Queue legend so that objects can be successively added (like matplotlib but
1435+
# expanding this feature so it works with multiple legends in same location)
1436+
if not args:
1437+
objs = []
1438+
elif len(args) == 1:
1439+
objs = args[0]
1440+
elif len(args) == 2:
1441+
objs = args[0]
1442+
kwargs.setdefault('labels', args[1])
1443+
else:
1444+
raise TypeError(f'Too many arguments. Expected 1 or 2, got {len(args)}.')
1445+
if queue:
1446+
self._queue_legend(loc, *objs, **kwargs)
1447+
else:
1448+
return legend_wrapper(self, *args, loc=loc, **kwargs)
14201449

14211450
def draw(self, renderer=None, *args, **kwargs):
14221451
# Perform extra post-processing steps

0 commit comments

Comments
 (0)