Skip to content

Commit 5130687

Browse files
committed
Partial commit
1 parent 40b3d79 commit 5130687

File tree

9 files changed

+594
-314
lines changed

9 files changed

+594
-314
lines changed

proplot/axes/base.py

+165-156
Large diffs are not rendered by default.

proplot/axes/cartesian.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from .. import ticker as pticker
1515
from ..config import rc
1616
from ..internals import ic # noqa: F401
17-
from ..internals import _not_none, _pop_rc, dependencies, docstring, texts, warnings
17+
from ..internals import _not_none, _pop_rc, dependencies, docstring, labels, warnings
1818
from . import plot, shared
1919

2020
__all__ = ['CartesianAxes']
@@ -371,7 +371,7 @@ def _apply_axis_sharing(self):
371371
if self._sharex is not None and axis.get_visible():
372372
level = 3 if self._panel_sharex_group else self.figure._sharex
373373
if level > 0:
374-
texts._transfer_text(axis.label, self._sharex.xaxis.label)
374+
labels._transfer_text(axis.label, self._sharex.xaxis.label)
375375
axis.label.set_visible(False)
376376
if level > 2:
377377
# WARNING: Cannot set NullFormatter because shared axes share the
@@ -382,7 +382,7 @@ def _apply_axis_sharing(self):
382382
if self._sharey is not None and axis.get_visible():
383383
level = 3 if self._panel_sharey_group else self.figure._sharey
384384
if level > 0:
385-
texts._transfer_text(axis.label, self._sharey.yaxis.label)
385+
labels._transfer_text(axis.label, self._sharey.yaxis.label)
386386
axis.label.set_visible(False)
387387
if level > 2:
388388
axis.set_tick_params(which='both', labelleft=False, labelright=False)

proplot/config.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -949,11 +949,11 @@ def _item_dicts(self, key, value, skip_cycle=False):
949949
kw_matplotlib['axes.prop_cycle'] = cycler.cycler('color', cmap.colors)
950950
kw_matplotlib['patch.facecolor'] = 'C0'
951951

952-
# Turning bounding box on should turn border off and vice versa
953-
elif contains('abc.bbox', 'title.bbox', 'abc.border', 'title.border'):
952+
# Turning box on should turn border off and vice versa
953+
elif contains('abc.box', 'title.box', 'abc.border', 'title.border'):
954954
if value:
955955
name, this = key.split('.')
956-
other = 'border' if this == 'bbox' else 'bbox'
956+
other = 'border' if this == 'box' else 'box'
957957
kw_proplot[name + '.' + other] = False
958958

959959
# Fontsize

proplot/figure.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
_translate_loc,
2828
context,
2929
docstring,
30-
texts,
30+
labels,
3131
warnings,
3232
)
3333
from .utils import units
@@ -1255,7 +1255,7 @@ def _update_axis_label(self, side, axs):
12551255
# Copy text from central label to spanning label
12561256
# NOTE: Must use spaces rather than newlines, otherwise tight layout
12571257
# won't make room. Reason is Text implementation (see Text._get_layout())
1258-
texts._transfer_text(axis.label, label) # text, color, and font properties
1258+
labels._transfer_text(axis.label, label) # text, color, and font properties
12591259
space = '\n'.join(' ' * (1 + label.get_text().count('\n')))
12601260
for axis in axislist: # should include original 'axis'
12611261
axis.label.set_text(space)

proplot/internals/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ def _not_none(*args, default=None, **kwargs):
5151
docstring,
5252
guides,
5353
inputs,
54+
labels,
5455
rcsetup,
55-
texts,
5656
warnings
5757
)
5858

@@ -405,6 +405,8 @@ def _translate_loc(loc, mode, *, default=None, **kwargs):
405405
if loc in (None, True):
406406
loc = default
407407
elif isinstance(loc, (str, Integral)):
408+
if isinstance(loc, str):
409+
loc = loc.lower()
408410
try:
409411
loc = loc_dict[loc]
410412
except KeyError:

proplot/internals/guides.py

+113-26
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
Utilties related to legends and colorbars.
44
"""
55
import matplotlib.artist as martist
6+
import matplotlib.axes as maxes
67
import matplotlib.colorbar as mcolorbar
7-
import matplotlib.legend as mlegend # noqa: F401
8+
import matplotlib.offsetbox as moffsetbox
9+
import matplotlib.projections as mprojections # noqa: F401
810
import matplotlib.ticker as mticker
11+
import matplotlib.transforms as mtransforms
912
import numpy as np
1013

1114
from . import ic # noqa: F401
1215
from . import warnings
1316

1417

15-
def _fill_guide_kw(kwargs, **pairs):
18+
def _fill_guide_kw(kwargs, overwrite=False, **pairs):
1619
"""
1720
Add the keyword arguments to the dictionary if not already present.
1821
"""
@@ -25,42 +28,48 @@ def _fill_guide_kw(kwargs, **pairs):
2528
if value is None:
2629
continue
2730
keys = tuple(a for group in aliases for a in group if key in group) # may be ()
28-
if not any(kwargs.get(key) is not None for key in keys): # note any(()) is True
31+
keys_found = tuple(key for key in keys if kwargs.get(key) is not None)
32+
if not keys_found:
2933
kwargs[key] = value
34+
elif overwrite: # overwrite existing key
35+
kwargs[keys_found[0]] = value
3036

3137

32-
def _guide_kw_from_obj(obj, name, kwargs):
38+
def _guide_kw_to_arg(name, kwargs, **pairs):
3339
"""
34-
Add to the dict from settings stored on the object if there are no conflicts.
40+
Add to the `colorbar_kw` or `legend_kw` dict if there are no conflicts.
3541
"""
36-
pairs = getattr(obj, f'_{name}_kw', None)
37-
pairs = pairs or {} # needed for some reason
38-
_fill_guide_kw(kwargs, **pairs)
39-
if isinstance(obj, (tuple, list, np.ndarray)):
40-
for iobj in obj: # possibly iterate over matplotlib tuple/list subclasses
41-
_guide_kw_from_obj(iobj, name, kwargs)
42-
return kwargs
42+
kw = kwargs.setdefault(f'{name}_kw', {})
43+
_fill_guide_kw(kw, overwrite=True, **pairs)
4344

4445

4546
def _guide_kw_to_obj(obj, name, kwargs):
4647
"""
47-
Add the guide keyword dict to the objects.
48+
Store settings on the object from the input dict.
4849
"""
50+
pairs = getattr(obj, f'_{name}_kw', None)
51+
pairs = pairs or {}
52+
_fill_guide_kw(pairs, overwrite=True, **kwargs) # update with current input
4953
try:
5054
setattr(obj, f'_{name}_kw', kwargs)
5155
except AttributeError:
5256
pass
5357
if isinstance(obj, (tuple, list, np.ndarray)):
54-
for iobj in obj:
55-
_guide_kw_to_obj(iobj, name, kwargs)
58+
for member in obj:
59+
_guide_kw_to_obj(member, name, kwargs)
5660

5761

58-
def _guide_kw_to_arg(name, kwargs, **pairs):
62+
def _guide_obj_to_kw(obj, name, kwargs):
5963
"""
60-
Add to the `colorbar_kw` or `legend_kw` dict if there are no conflicts.
64+
Add to the dict from settings stored on the object if there are no conflicts.
6165
"""
62-
kw = kwargs.setdefault(f'{name}_kw', {})
63-
_fill_guide_kw(kw, **pairs)
66+
pairs = getattr(obj, f'_{name}_kw', None)
67+
pairs = pairs or {}
68+
_fill_guide_kw(kwargs, overwrite=False, **pairs) # update from previous input
69+
if isinstance(obj, (tuple, list, np.ndarray)):
70+
for member in obj: # possibly iterate over matplotlib tuple/list subclasses
71+
_guide_obj_to_kw(member, name, kwargs)
72+
return kwargs
6473

6574

6675
def _iter_children(*args):
@@ -117,15 +126,93 @@ def _update_ticks(self, manual_only=False):
117126
self.minorticks_on() # at least turn them on
118127

119128

120-
class _InsetColorbar(martist.Artist):
121-
"""
122-
Legend-like class for managing inset colorbars.
123-
"""
124-
# TODO: Write this!
129+
class _AnchoredAxes(moffsetbox.AnchoredOffsetbox):
130+
"""
131+
An anchored child axes whose background patch and offset position is determined
132+
by the tight bounding box. Analogous to `~matplotlib.offsetbox.AnchoredText`.
133+
"""
134+
def __init__(self, ax, width, height, **kwargs):
135+
# Note the default bbox_to_anchor will be
136+
# the axes bounding box.
137+
bounds = [0, 0, 1, 1] # arbitrary initial bounds
138+
child = maxes.Axes(ax.figure, bounds, zorder=self.zorder)
139+
# cls = mprojections.get_projection_class('proplot_cartesian') # TODO
140+
# child = cls(ax.figure, bounds, zorder=self.zorder)
141+
super().__init__(child=child, bbox_to_anchor=ax.bbox, **kwargs)
142+
ax.add_artist(self) # sets self.axes to ax and bbox_to_anchor to ax.bbox
143+
self._child = child # ensure private attribute exists
144+
self._width = width
145+
self._height = height
146+
147+
def draw(self, renderer):
148+
# Just draw the patch (not the axes)
149+
if not self.get_visible():
150+
return
151+
if hasattr(self, '_update_offset_func'):
152+
self._update_offset_func(renderer)
153+
else:
154+
warnings._warn_proplot(
155+
'Failed to update _AnchoredAxes offset function due to matplotlib '
156+
'private API change. The resulting axes position may be incorrect.'
157+
)
158+
bbox = self.get_window_extent(renderer)
159+
self._update_patch(renderer, bbox=bbox)
160+
bbox = self.get_child_extent(renderer, offset=True)
161+
self._update_child(bbox)
162+
self.patch.draw(renderer)
163+
self._child.draw(renderer)
164+
165+
def _update_child(self, bbox):
166+
# Update the child bounding box
167+
trans = getattr(self.figure, 'transSubfigure', self.figure.transFigure)
168+
bbox = mtransforms.TransformedBbox(bbox, trans.inverted())
169+
getattr(self._child, '_set_position', self._child.set_position)(bbox)
170+
171+
def _update_patch(self, renderer, bbox):
172+
# Update the patch position
173+
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
174+
self.patch.set_bounds(bbox.x0, bbox.y0, bbox.width, bbox.height)
175+
self.patch.set_mutation_scale(fontsize)
176+
177+
def get_extent(self, renderer, offset=False):
178+
# Return the extent of the child plus padding
179+
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
180+
pad = self.pad * fontsize
181+
bbox = self._child._tight_bbox = self._child.get_tightbbox(renderer)
182+
# bbox = self._child.get_tightbbox(renderer, use_cache=True) # TODO
183+
width = bbox.width + 2 * pad
184+
height = bbox.height + 2 * pad
185+
xd = yd = pad
186+
if offset:
187+
xd += self._child.bbox.x0 - bbox.x0
188+
yd += self._child.bbox.y0 - bbox.y0
189+
return width, height, xd, yd
190+
191+
def get_child_extent(self, renderer, offset=False):
192+
# Update the child position
193+
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
194+
x0, y0 = self._child.bbox.x0, self._child.bbox.y0
195+
if offset: # find offset position
196+
self._update_child(self.get_child_extent(renderer))
197+
width, height, xd, yd = self.get_extent(renderer, offset=True)
198+
x0, y0 = self.get_offset(width, height, xd, yd, renderer)
199+
# bbox = self._child.get_tightbbox(use_cache=True) # TODO
200+
xd += self._child.bbox.x0 - self._child._tight_bbox.x0
201+
yd += self._child.bbox.y0 - self._child._tight_bbox.y0
202+
width, height = self._width * fontsize, self._height * fontsize
203+
return mtransforms.Bbox.from_bounds(x0, y0, width, height)
204+
205+
def get_window_extent(self, renderer):
206+
# Return the window bounding box
207+
self._child.get_tightbbox(renderer) # reset the cache
208+
self._update_child(self.get_child_extent(renderer))
209+
xi, yi, xd, yd = self.get_extent(renderer, offset=False)
210+
ox, oy = self.get_offset(xi, yi, xd, yd, renderer)
211+
return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, xi, yi)
125212

126213

127214
class _CenteredLegend(martist.Artist):
128215
"""
129-
Legend-like class for managing centered-row legends.
216+
A legend-like subclass whose handles are grouped into centered rows of
217+
`~matplotlib.offsetbox.HPacker` rather than `~matplotlib.offsetbox.VPacker` columns.
130218
"""
131-
# TODO: Write this!

0 commit comments

Comments
 (0)