Skip to content

Commit

Permalink
Partial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
lukelbd committed Jan 15, 2022
1 parent 40b3d79 commit 5130687
Show file tree
Hide file tree
Showing 9 changed files with 594 additions and 314 deletions.
321 changes: 165 additions & 156 deletions proplot/axes/base.py

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions proplot/axes/cartesian.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from .. import ticker as pticker
from ..config import rc
from ..internals import ic # noqa: F401
from ..internals import _not_none, _pop_rc, dependencies, docstring, texts, warnings
from ..internals import _not_none, _pop_rc, dependencies, docstring, labels, warnings
from . import plot, shared

__all__ = ['CartesianAxes']
Expand Down Expand Up @@ -371,7 +371,7 @@ def _apply_axis_sharing(self):
if self._sharex is not None and axis.get_visible():
level = 3 if self._panel_sharex_group else self.figure._sharex
if level > 0:
texts._transfer_text(axis.label, self._sharex.xaxis.label)
labels._transfer_text(axis.label, self._sharex.xaxis.label)
axis.label.set_visible(False)
if level > 2:
# WARNING: Cannot set NullFormatter because shared axes share the
Expand All @@ -382,7 +382,7 @@ def _apply_axis_sharing(self):
if self._sharey is not None and axis.get_visible():
level = 3 if self._panel_sharey_group else self.figure._sharey
if level > 0:
texts._transfer_text(axis.label, self._sharey.yaxis.label)
labels._transfer_text(axis.label, self._sharey.yaxis.label)
axis.label.set_visible(False)
if level > 2:
axis.set_tick_params(which='both', labelleft=False, labelright=False)
Expand Down
6 changes: 3 additions & 3 deletions proplot/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -949,11 +949,11 @@ def _item_dicts(self, key, value, skip_cycle=False):
kw_matplotlib['axes.prop_cycle'] = cycler.cycler('color', cmap.colors)
kw_matplotlib['patch.facecolor'] = 'C0'

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

# Fontsize
Expand Down
4 changes: 2 additions & 2 deletions proplot/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
_translate_loc,
context,
docstring,
texts,
labels,
warnings,
)
from .utils import units
Expand Down Expand Up @@ -1255,7 +1255,7 @@ def _update_axis_label(self, side, axs):
# Copy text from central label to spanning label
# NOTE: Must use spaces rather than newlines, otherwise tight layout
# won't make room. Reason is Text implementation (see Text._get_layout())
texts._transfer_text(axis.label, label) # text, color, and font properties
labels._transfer_text(axis.label, label) # text, color, and font properties
space = '\n'.join(' ' * (1 + label.get_text().count('\n')))
for axis in axislist: # should include original 'axis'
axis.label.set_text(space)
Expand Down
4 changes: 3 additions & 1 deletion proplot/internals/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ def _not_none(*args, default=None, **kwargs):
docstring,
guides,
inputs,
labels,
rcsetup,
texts,
warnings
)

Expand Down Expand Up @@ -405,6 +405,8 @@ def _translate_loc(loc, mode, *, default=None, **kwargs):
if loc in (None, True):
loc = default
elif isinstance(loc, (str, Integral)):
if isinstance(loc, str):
loc = loc.lower()
try:
loc = loc_dict[loc]
except KeyError:
Expand Down
139 changes: 113 additions & 26 deletions proplot/internals/guides.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@
Utilties related to legends and colorbars.
"""
import matplotlib.artist as martist
import matplotlib.axes as maxes
import matplotlib.colorbar as mcolorbar
import matplotlib.legend as mlegend # noqa: F401
import matplotlib.offsetbox as moffsetbox
import matplotlib.projections as mprojections # noqa: F401
import matplotlib.ticker as mticker
import matplotlib.transforms as mtransforms
import numpy as np

from . import ic # noqa: F401
from . import warnings


def _fill_guide_kw(kwargs, **pairs):
def _fill_guide_kw(kwargs, overwrite=False, **pairs):
"""
Add the keyword arguments to the dictionary if not already present.
"""
Expand All @@ -25,42 +28,48 @@ def _fill_guide_kw(kwargs, **pairs):
if value is None:
continue
keys = tuple(a for group in aliases for a in group if key in group) # may be ()
if not any(kwargs.get(key) is not None for key in keys): # note any(()) is True
keys_found = tuple(key for key in keys if kwargs.get(key) is not None)
if not keys_found:
kwargs[key] = value
elif overwrite: # overwrite existing key
kwargs[keys_found[0]] = value


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


def _guide_kw_to_obj(obj, name, kwargs):
"""
Add the guide keyword dict to the objects.
Store settings on the object from the input dict.
"""
pairs = getattr(obj, f'_{name}_kw', None)
pairs = pairs or {}
_fill_guide_kw(pairs, overwrite=True, **kwargs) # update with current input
try:
setattr(obj, f'_{name}_kw', kwargs)
except AttributeError:
pass
if isinstance(obj, (tuple, list, np.ndarray)):
for iobj in obj:
_guide_kw_to_obj(iobj, name, kwargs)
for member in obj:
_guide_kw_to_obj(member, name, kwargs)


def _guide_kw_to_arg(name, kwargs, **pairs):
def _guide_obj_to_kw(obj, name, kwargs):
"""
Add to the `colorbar_kw` or `legend_kw` dict if there are no conflicts.
Add to the dict from settings stored on the object if there are no conflicts.
"""
kw = kwargs.setdefault(f'{name}_kw', {})
_fill_guide_kw(kw, **pairs)
pairs = getattr(obj, f'_{name}_kw', None)
pairs = pairs or {}
_fill_guide_kw(kwargs, overwrite=False, **pairs) # update from previous input
if isinstance(obj, (tuple, list, np.ndarray)):
for member in obj: # possibly iterate over matplotlib tuple/list subclasses
_guide_obj_to_kw(member, name, kwargs)
return kwargs


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


class _InsetColorbar(martist.Artist):
"""
Legend-like class for managing inset colorbars.
"""
# TODO: Write this!
class _AnchoredAxes(moffsetbox.AnchoredOffsetbox):
"""
An anchored child axes whose background patch and offset position is determined
by the tight bounding box. Analogous to `~matplotlib.offsetbox.AnchoredText`.
"""
def __init__(self, ax, width, height, **kwargs):
# Note the default bbox_to_anchor will be
# the axes bounding box.
bounds = [0, 0, 1, 1] # arbitrary initial bounds
child = maxes.Axes(ax.figure, bounds, zorder=self.zorder)
# cls = mprojections.get_projection_class('proplot_cartesian') # TODO
# child = cls(ax.figure, bounds, zorder=self.zorder)
super().__init__(child=child, bbox_to_anchor=ax.bbox, **kwargs)
ax.add_artist(self) # sets self.axes to ax and bbox_to_anchor to ax.bbox
self._child = child # ensure private attribute exists
self._width = width
self._height = height

def draw(self, renderer):
# Just draw the patch (not the axes)
if not self.get_visible():
return
if hasattr(self, '_update_offset_func'):
self._update_offset_func(renderer)
else:
warnings._warn_proplot(
'Failed to update _AnchoredAxes offset function due to matplotlib '
'private API change. The resulting axes position may be incorrect.'
)
bbox = self.get_window_extent(renderer)
self._update_patch(renderer, bbox=bbox)
bbox = self.get_child_extent(renderer, offset=True)
self._update_child(bbox)
self.patch.draw(renderer)
self._child.draw(renderer)

def _update_child(self, bbox):
# Update the child bounding box
trans = getattr(self.figure, 'transSubfigure', self.figure.transFigure)
bbox = mtransforms.TransformedBbox(bbox, trans.inverted())
getattr(self._child, '_set_position', self._child.set_position)(bbox)

def _update_patch(self, renderer, bbox):
# Update the patch position
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
self.patch.set_bounds(bbox.x0, bbox.y0, bbox.width, bbox.height)
self.patch.set_mutation_scale(fontsize)

def get_extent(self, renderer, offset=False):
# Return the extent of the child plus padding
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
pad = self.pad * fontsize
bbox = self._child._tight_bbox = self._child.get_tightbbox(renderer)
# bbox = self._child.get_tightbbox(renderer, use_cache=True) # TODO
width = bbox.width + 2 * pad
height = bbox.height + 2 * pad
xd = yd = pad
if offset:
xd += self._child.bbox.x0 - bbox.x0
yd += self._child.bbox.y0 - bbox.y0
return width, height, xd, yd

def get_child_extent(self, renderer, offset=False):
# Update the child position
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
x0, y0 = self._child.bbox.x0, self._child.bbox.y0
if offset: # find offset position
self._update_child(self.get_child_extent(renderer))
width, height, xd, yd = self.get_extent(renderer, offset=True)
x0, y0 = self.get_offset(width, height, xd, yd, renderer)
# bbox = self._child.get_tightbbox(use_cache=True) # TODO
xd += self._child.bbox.x0 - self._child._tight_bbox.x0
yd += self._child.bbox.y0 - self._child._tight_bbox.y0
width, height = self._width * fontsize, self._height * fontsize
return mtransforms.Bbox.from_bounds(x0, y0, width, height)

def get_window_extent(self, renderer):
# Return the window bounding box
self._child.get_tightbbox(renderer) # reset the cache
self._update_child(self.get_child_extent(renderer))
xi, yi, xd, yd = self.get_extent(renderer, offset=False)
ox, oy = self.get_offset(xi, yi, xd, yd, renderer)
return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, xi, yi)


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

0 comments on commit 5130687

Please sign in to comment.