8585 can be combined with `xtickrange` and `ytickrange` to make "stacked" line plots.
8686xloc, 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).
106108xtickdir, ytickdir, tickdir : {'out', 'in', 'inout'}, optional
255257_alt_descrip = """
256258Add an axes locked to the same location with a
257259distinct {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
260262two {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