Source code for proplot.axes.polar

#!/usr/bin/env python3
"""
Polar axes using azimuth and radius instead of *x* and *y*.
"""
import matplotlib.projections as mproj
import numpy as np
import matplotlib.ticker as mticker
from . import base
from .. import ticker as pticker
from .. import constructor
from ..config import rc
from ..internals import ic  # noqa: F401
from ..internals import docstring, warnings, _not_none

__all__ = ['PolarAxes']


[docs]class PolarAxes(base.Axes, mproj.PolarAxes): """ Axes subclass for plotting in polar coordinates. Adds the `~PolarAxes.format` method and overrides several existing methods. """ #: The registered projection name. name = 'polar2' def __init__(self, *args, **kwargs): """ See also -------- proplot.ui.subplots """ # Set tick length to zero so azimuthal labels are not too offset # Change default radial axis formatter but keep default theta one super().__init__(*args, **kwargs) formatter = pticker.AutoFormatter() self.yaxis.set_major_formatter(formatter) self.yaxis.isDefault_majfmt = True for axis in (self.xaxis, self.yaxis): axis.set_tick_params(which='both', size=0)
[docs] @docstring.add_snippets def format( self, *args, r0=None, theta0=None, thetadir=None, thetamin=None, thetamax=None, thetalim=None, rmin=None, rmax=None, rlim=None, rlabelpos=None, rscale=None, rborder=None, thetalocator=None, rlocator=None, thetalines=None, rlines=None, thetaformatter=None, rformatter=None, thetalabels=None, rlabels=None, thetalocator_kw=None, rlocator_kw=None, thetaformatter_kw=None, rformatter_kw=None, patch_kw=None, **kwargs ): """ Modify radial gridline locations, gridline labels, limits, and more. Unknown keyword arguments are passed to `Axes.format` and `~proplot.config.RcConfigurator.context`. All ``theta`` arguments are specified in *degrees*, not radians. The below parameters are specific to `PolarAxes`. Parameters ---------- r0 : float, optional The radial origin. theta0 : {'N', 'NW', 'W', 'SW', 'S', 'SE', 'E', 'NE'} The zero azimuth location. thetadir : {1, -1, 'anticlockwise', 'counterclockwise', 'clockwise'}, \ optional The positive azimuth direction. Clockwise corresponds to ``-1`` and anticlockwise corresponds to ``1``. Default is ``1``. thetamin, thetamax : float, optional The lower and upper azimuthal bounds in degrees. If ``thetamax != thetamin + 360``, this produces a sector plot. thetalim : (float, float), optional Specifies `thetamin` and `thetamax` at once. rmin, rmax : float, optional The inner and outer radial limits. If ``r0 != rmin``, this produces an annular plot. rlim : (float, float), optional Specifies `rmin` and `rmax` at once. rborder : bool, optional Toggles the polar axes border on and off. Visibility of the "inner" radial spine and "start" and "end" azimuthal spines is controlled automatically be matplotlib. thetalocator, rlocator : float or list of float, optional Used to determine the azimuthal and radial gridline positions. Passed to the `~proplot.constructor.Locator` constructor. Can be float, list of float, string, or `matplotlib.ticker.Locator` instance. thetalines, rlines Aliases for `thetalocator`, `rlocator`. thetalocator_kw, rlocator_kw : dict-like, optional The azimuthal and radial locator settings. Passed to `~proplot.constructor.Locator`. rlabelpos : float, optional The azimuth at which radial coordinates are labeled. thetaformatter, rformatter : formatter spec, optional Used to determine the azimuthal and radial label format. Passed to the `~proplot.constructor.Formatter` constructor. Can be string, list of string, or `matplotlib.ticker.Formatter` instance. Use ``[]`` or ``'null'`` for no ticks. thetalabels, rlabels : optional Aliases for `thetaformatter`, `rformatter`. thetaformatter_kw, rformatter_kw : dict-like, optional The azimuthal and radial label formatter settings. Passed to `~proplot.constructor.Formatter`. %(axes.patch_kw)s Other parameters ---------------- %(axes.other)s See also -------- proplot.config.RcConfigurator.context proplot.axes.Axes.format """ rc_kw, rc_mode, kwargs = self._parse_format(**kwargs) with rc.context(rc_kw, mode=rc_mode): # Background patch kw_face = rc.fill( { 'facecolor': 'axes.facecolor', 'alpha': 'axes.alpha' }, context=True, ) patch_kw = patch_kw or {} kw_face.update(patch_kw) self.patch.update(kw_face) # Not mutable default args thetalocator_kw = thetalocator_kw or {} thetaformatter_kw = thetaformatter_kw or {} rlocator_kw = rlocator_kw or {} rformatter_kw = rformatter_kw or {} # Flexible input if rlim is not None: if rmin is not None or rmax is not None: warnings._warn_proplot( f'Conflicting keyword args rmin={rmin}, rmax={rmax}, ' f'and rlim={rlim}. Using "rlim".' ) rmin, rmax = rlim if thetalim is not None: if thetamin is not None or thetamax is not None: warnings._warn_proplot( f'Conflicting keyword args thetamin={thetamin}, ' f'thetamax={thetamax}, and thetalim={thetalim}. ' f'Using "thetalim".' ) thetamin, thetamax = thetalim thetalocator = _not_none( thetalines=thetalines, thetalocator=thetalocator, ) thetaformatter = _not_none( thetalabels=thetalabels, thetaformatter=thetaformatter, ) rlocator = _not_none( rlines=rlines, rlocator=rlocator, ) rformatter = _not_none( rlabels=rlabels, rformatter=rformatter, ) # Special radius settings if r0 is not None: self.set_rorigin(r0) if rlabelpos is not None: self.set_rlabel_position(rlabelpos) if rscale is not None: self.set_rscale(rscale) if rborder is not None: self.spines['polar'].set_visible(bool(rborder)) # Special azimuth settings if theta0 is not None: self.set_theta_zero_location(theta0) if thetadir is not None: self.set_theta_direction(thetadir) # Iterate for ( x, r, axis, min_, max_, locator, formatter, locator_kw, formatter_kw, ) in zip( ('x', 'y'), ('theta', 'r'), (self.xaxis, self.yaxis), (thetamin, rmin), (thetamax, rmax), (thetalocator, rlocator), (thetaformatter, rformatter), (thetalocator_kw, rlocator_kw), (thetaformatter_kw, rformatter_kw) ): # Axis limits # Try to use public API where possible if min_ is not None: getattr(self, 'set_' + r + 'min')(min_) else: min_ = getattr(self, 'get_' + r + 'min')() if max_ is not None: getattr(self, 'set_' + r + 'max')(max_) else: max_ = getattr(self, 'get_' + r + 'max')() # Spine settings kw = rc.fill( { 'linewidth': 'axes.linewidth', 'color': 'axes.edgecolor', }, context=True, ) sides = ('inner', 'polar') if r == 'r' else ('start', 'end') spines = [self.spines[side] for side in sides] for spine, side in zip(spines, sides): spine.update(kw) # Grid and grid label settings # NOTE: Not sure if polar lines inherit tick or grid props kw = rc.fill( { 'color': x + 'tick.color', 'labelcolor': 'tick.labelcolor', # new props 'labelsize': 'tick.labelsize', 'grid_color': 'grid.color', 'grid_alpha': 'grid.alpha', 'grid_linewidth': 'grid.linewidth', 'grid_linestyle': 'grid.linestyle', }, context=True, ) axis.set_tick_params(which='both', **kw) # Label settings that can't be controlled with set_tick_params kw = rc.fill( { 'fontfamily': 'font.family', 'weight': 'tick.labelweight' }, context=True, ) for t in axis.get_ticklabels(): t.update(kw) # Tick locator, which in this case applies to gridlines # NOTE: Must convert theta locator input to radians, then back # to degrees. if locator is not None: if r == 'theta' and ( not isinstance(locator, (str, mticker.Locator))): # real axis limts are rad locator = np.deg2rad(locator) locator = constructor.Locator(locator, **locator_kw) locator.set_axis(axis) # this is what set_locator does grids = np.array(locator()) if r == 'r': grids = grids[(grids >= min_) & (grids <= max_)] self.set_rgrids(grids) else: grids = np.rad2deg(grids) grids = grids[(grids >= min_) & (grids <= max_)] if grids[-1] == min_ + 360: # exclusive if 360 degrees grids = grids[:-1] self.set_thetagrids(grids) # Tick formatter and toggling if formatter is not None: formatter = constructor.Formatter(formatter, **formatter_kw) # noqa: E501 axis.set_major_formatter(formatter) # Parent method super().format(*args, **kwargs)