Skip to content

Commit 3154b1a

Browse files
committed
Cleanup _update_locs and support single-char locs
1 parent 351e618 commit 3154b1a

File tree

2 files changed

+65
-86
lines changed

2 files changed

+65
-86
lines changed

proplot/axes/base.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,8 @@
282282
Other parameters
283283
----------------
284284
**kwargs
285-
Passed to `proplot.axes.CartesianAxes`.
285+
Passed to `proplot.axes.CartesianAxes`. Supports all valid
286+
`~proplot.axes.CartesianAxes.format` keywords.
286287
287288
Returns
288289
-------
@@ -781,8 +782,8 @@ def __init__(self, *args, **kwargs):
781782
self._panel_align = {} # store 'align' and 'length' for "filled" cbar/legend
782783
self._panel_parent = None
783784
self._panel_share = False
784-
self._panel_sharex_group = False
785-
self._panel_sharey_group = False
785+
self._panel_sharex_group = False # see _apply_auto_share
786+
self._panel_sharey_group = False # see _apply_auto_share
786787
self._panel_side = None
787788
self._tight_bbox = None # bounding boxes are saved
788789
self.xaxis.isDefault_minloc = True # ensure enabled at start (needed for dual)
@@ -1344,8 +1345,8 @@ def _apply_auto_share(self):
13441345
vertical extent of subplots in the figure gridspec.
13451346
"""
13461347
# Panel axes sharing, between main subplot and its panels
1347-
# NOTE: _panel_share means "include this panel in the axis sharing group"
1348-
# while _panel_sharex_group indicates the group itself and may include main axes
1348+
# NOTE: _panel_share means "include this panel in the axis sharing group" while
1349+
# _panel_sharex_group indicates the group itself and may include main axes
13491350
def shared(paxs):
13501351
return [pax for pax in paxs if not pax._panel_hidden and pax._panel_share]
13511352

proplot/axes/cartesian.py

Lines changed: 59 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -85,22 +85,24 @@
8585
can be combined with `xtickrange` and `ytickrange` to make "stacked" line plots.
8686
xloc, yloc : optional
8787
Shorthands for `xspineloc`, `yspineloc`.
88-
xspineloc, yspineloc : {'bottom', 'top', 'left', 'right', \
88+
xspineloc, yspineloc : {'b', 't', 'l', 'r', 'bottom', 'top', 'left', 'right', \
8989
'both', 'neither', 'none', 'zero', 'center'} or 2-tuple, optional
9090
The x and y spine locations. Applied with `~matplotlib.spines.Spine.set_position`.
9191
Propagates to `tickloc` unless specified otherwise.
92-
xtickloc, ytickloc \
93-
: {'bottom', 'top', 'left', 'right', 'both', 'neither', 'none'}, optional
92+
xtickloc, ytickloc : {'b', 't', 'l', 'r', 'bottom', 'top', 'left', 'right', \
93+
'both', 'neither', 'none'}, optional
9494
Which x and y axis spines should have major and minor tick marks. Inherits from
9595
`spineloc` by default and propagates to `ticklabelloc` unless specified otherwise.
96-
xticklabelloc, yticklabelloc \
97-
: {'bottom', 'top', 'left', 'right', 'both', 'neither', 'none'}, optional
96+
xticklabelloc, yticklabelloc : {'b', 't', 'l', 'r', 'bottom', 'top', 'left', 'right', \
97+
'both', 'neither', 'none'}, optional
9898
Which x and y axis spines should have major tick labels. Inherits from `tickloc`
9999
by default and propagates to `labelloc` and `offsetloc` unless specified otherwise.
100-
xlabelloc, ylabelloc : {'bottom', 'top', 'left', 'right'}, optional
100+
xlabelloc, ylabelloc : \
101+
{'b', 't', 'l', 'r', 'bottom', 'top', 'left', 'right'}, optional
101102
Which x and y axis spines should have axis labels. Inherits from
102103
`ticklabelloc` by default (if `ticklabelloc` is a single side).
103-
xoffsetloc, yoffsetloc : {'left', 'right'}, optional
104+
xoffsetloc, yoffsetloc : \
105+
{'b', 't', 'l', 'r', 'bottom', 'top', 'left', 'right'}, optional
104106
Which x and y axis spines should have the axis offset indicator. Inherits from
105107
`ticklabelloc` by default (if `ticklabelloc` is a single side).
106108
xtickdir, ytickdir, tickdir : {'out', 'in', 'inout'}, optional
@@ -255,7 +257,7 @@
255257
_alt_descrip = """
256258
Add an axes locked to the same location with a
257259
distinct {x} axis.
258-
This is an alias and possibly more intuitive name for
260+
This is an alias and arguably more intuitive name for
259261
`~proplot.axes.CartesianAxes.twin{y}`, which generates
260262
two {x} axes with a shared ("twin") {y} axes.
261263
"""
@@ -506,9 +508,11 @@ def _get_spine_side(self, s, loc):
506508
Get the spine side implied by the input location or position. This
507509
propagates to tick mark, tick label, and axis label positions.
508510
"""
511+
# NOTE: Could defer error to CartesianAxes.format but instead use our
512+
# own error message with info on coordinate position options.
509513
sides = ('bottom', 'top') if s == 'x' else ('left', 'right')
510514
centers = ('zero', 'center')
511-
options = (*sides, 'both', 'neither', 'none')
515+
options = (*(s[0] for s in sides), *sides, 'both', 'neither', 'none')
512516
if np.iterable(loc) and len(loc) == 2 and loc[0] in ('axes', 'data', 'outward'):
513517
lim = getattr(self, f'get_{s}lim')()
514518
if loc[0] == 'outward': # ambiguous so just choose first side
@@ -517,7 +521,7 @@ def _get_spine_side(self, s, loc):
517521
side = sides[int(loc[1] > 0.5)]
518522
else:
519523
side = sides[int(loc[1] > lim[0] + 0.5 * (lim[1] - lim[0]))]
520-
elif loc in centers: # ambiguous so just choose first side
524+
elif loc in centers: # ambiguous so just choose the first side
521525
side = sides[0]
522526
elif loc is None or loc in options:
523527
side = loc
@@ -793,38 +797,32 @@ def _update_spines(self, s, *, loc=None, bounds=None):
793797
"""
794798
Update the spine settings.
795799
"""
796-
# Iterate over spines associated with this axis
800+
# Change default spine location from 'both' to the first
801+
# relevant side if the user passes 'bounds'.
797802
sides = ('bottom', 'top') if s == 'x' else ('left', 'right')
798-
pside = self._get_spine_side(s, loc) # side for set_position()
803+
opts = (*(s[0] for s in sides), *sides) # see _get_spine_side()
804+
side = self._get_spine_side(s, loc) # side for set_position()
799805
if bounds is not None and all(self.spines[s].get_visible() for s in sides):
800806
loc = _not_none(loc, sides[0])
801-
for side in sides:
802-
# Change default spine location from 'both' to the first relevant
803-
# side if the user passes 'bounds'.
804-
spine = self.spines[side]
805-
# Eliminate sides
807+
for key in sides:
808+
# Simple spine location that just toggles the side(s). Do not bother
809+
# with the _get_spine_side stuff.
810+
spine = self.spines[key]
806811
if loc is None:
807812
pass
808813
elif loc == 'neither' or loc == 'none':
809814
spine.set_visible(False)
810815
elif loc == 'both':
811816
spine.set_visible(True)
812-
elif loc in sides: # make relevant spine visible
813-
spine.set_visible(side == loc)
817+
elif loc in opts:
818+
spine.set_visible(key[0] == loc[0])
814819
# Special spine location, usually 'zero', 'center', or tuple with
815820
# (units, location) where 'units' can be 'axes', 'data', or 'outward'.
816-
# Matplotlib internally represents these with 'bottom' and 'left'.
817-
elif pside != side:
818-
spine.set_visible(False)
821+
elif key != side:
822+
spine.set_visible(False) # special position is for other spine
819823
else:
820-
spine.set_visible(True)
821-
try:
822-
spine.set_position(loc)
823-
except ValueError:
824-
raise ValueError(
825-
f'Invalid {s} spine location {loc!r}. Options are: '
826-
+ ', '.join(map(repr, (*sides, 'both', 'neither'))) + '.'
827-
)
824+
spine.set_visible(True) # special position uses this spine
825+
spine.set_position(loc)
828826
# Apply spine bounds
829827
if bounds is not None:
830828
spine.set_bounds(*bounds)
@@ -835,67 +833,47 @@ def _update_locs(
835833
"""
836834
Update the tick, tick label, and axis label locations.
837835
"""
838-
# The tick and tick label sides for Cartesian axes
839-
kw = {}
836+
# Helper function and initial stuff
837+
def _validate_loc(loc, opts, descrip):
838+
try:
839+
return opts[loc]
840+
except KeyError:
841+
raise ValueError(
842+
f'Invalid {descrip} location {loc!r}. Options are '
843+
+ ', '.join(map(repr, sides + tuple(opts))) + '.'
844+
)
840845
sides = ('bottom', 'top') if s == 'x' else ('left', 'right')
841-
sides_map = {'both': sides, 'neither': (), 'none': (), None: None}
842846
sides_active = tuple(side for side in sides if self.spines[side].get_visible())
847+
label_opts = {s[:i]: s for s in sides for i in (1, None)}
848+
tick_opts = {'both': sides, 'neither': (), 'none': (), None: None}
849+
tick_opts.update({k: (v,) for k, v in label_opts.items()})
843850

844-
# The tick side(s)
845-
# NOTE: Silently forbids adding ticks to sides with invisible spines
846-
ticklocs = sides_map.get(tickloc, (tickloc,))
847-
if ticklocs and any(loc not in sides for loc in ticklocs):
848-
raise ValueError(
849-
f'Invalid tick mark location {tickloc!r}. Options are '
850-
+ ', '.join(map(repr, sides + tuple(sides_map))) + '.'
851-
)
852-
if ticklocs is not None:
853-
kw.update({side: side in ticklocs for side in sides})
854-
kw.update(
855-
{
856-
side: False for side in sides if side not in sides_active
857-
}
858-
)
859-
860-
# The tick label side(s). Make sure these only appear where ticks are
861-
# NOTE: Silently forbids adding labels to sides with invisible ticks or spines
862-
ticklabellocs = sides_map.get(ticklabelloc, (ticklabelloc,))
863-
if ticklabellocs and any(loc not in sides for loc in ticklabellocs):
864-
raise ValueError(
865-
f'Invalid tick label location {ticklabelloc!r}. Options are '
866-
+ ', '.join(map(repr, sides + tuple(sides_map))) + '.'
867-
)
868-
if ticklabellocs is not None:
869-
kw.update({'label' + side: (side in ticklabellocs) for side in sides})
870-
kw.update(
871-
{
872-
'label' + side: False for side in sides
873-
if side not in sides_active
874-
or ticklocs is not None and side not in ticklocs
875-
}
876-
)
877-
878-
# The axis label side(s)
879-
# NOTE: Silently forbids adding labels and offsets to sides with missing spines
880-
if ticklocs is not None:
881-
options = tuple(_ for _ in sides if _ in ticklocs and _ in sides_active)
882-
if len(options) == 1:
883-
labelloc = _not_none(labelloc, options[0])
884-
offsetloc = _not_none(offsetloc, options[0])
885-
if labelloc is not None and labelloc not in sides:
886-
raise ValueError(
887-
f'Invalid label location {labelloc!r}. Options are '
888-
+ ', '.join(map(repr, sides)) + '.'
889-
)
851+
# Apply the tick mark and tick label locations
852+
kw = {}
853+
kw.update({side: False for side in sides if side not in sides_active})
854+
kw.update({'label' + side: False for side in sides if side not in sides_active})
855+
if ticklabelloc is not None:
856+
ticklabelloc = _validate_loc(ticklabelloc, tick_opts, 'tick label')
857+
kw.update({'label' + side: side in ticklabelloc for side in sides})
858+
if tickloc is not None: # possibly overrides ticklabelloc
859+
tickloc = _validate_loc(tickloc, tick_opts, 'tick mark')
860+
kw.update({side: side in tickloc for side in sides})
861+
kw.update({'label' + side: False for side in sides if side not in tickloc})
862+
self.tick_params(axis=s, which='both', **kw)
890863

891-
# Apply the tick, tick label, and label locations
864+
# Apply the axis label and offset label locations
892865
# Uses ugly mpl 3.3+ tick_top() tick_bottom() kludge for offset location
893866
# See: https://matplotlib.org/3.3.1/users/whats_new.html
894867
axis = getattr(self, f'{s}axis')
895-
self.tick_params(axis=s, which='both', **kw)
868+
options = tuple(_ for _ in sides if tickloc and _ in tickloc and _ in sides_active) # noqa: E501
869+
if tickloc is not None and len(options) == 1:
870+
labelloc = _not_none(labelloc, options[0])
871+
offsetloc = _not_none(offsetloc, options[0])
896872
if labelloc is not None:
873+
labelloc = _validate_loc(labelloc, label_opts, 'axis label')
897874
axis.set_label_position(labelloc)
898875
if offsetloc is not None:
876+
offsetloc = _not_none(offsetloc, options[0])
899877
if hasattr(axis, 'set_offset_position'): # y axis (and future x axis?)
900878
axis.set_offset_position(offsetloc)
901879
elif s == 'x' and _version_mpl >= '3.3': # ugly x axis kludge

0 commit comments

Comments
 (0)