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
from .. import constructor
from .. import ticker as pticker
from ..config import _parse_format, rc
from ..internals import ic # noqa: F401
from ..internals import _not_none, docstring, warnings
from . import plot, shared
__all__ = ['PolarAxes']
[docs]class PolarAxes(shared._SharedAxes, plot.PlotAxes, mproj.PolarAxes):
"""
Axes subclass for plotting in polar coordinates. Adds the `~PolarAxes.format`
method and overrides several existing methods.
"""
#: The registered projection name.
name = 'proplot_polar'
def __init__(self, *args, **kwargs):
"""
See also
--------
proplot.ui.subplots
proplot.axes.Axes
proplot.axes.PlotAxes
matplotlib.projections.PolarAxes
"""
# 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)
def _update_formatter(self, x, *, formatter=None, formatter_kw=None):
"""
Update the gridline label formatter.
"""
# Tick formatter and toggling
axis = getattr(self, x + 'axis')
formatter_kw = formatter_kw or {}
if formatter is not None:
formatter = constructor.Formatter(formatter, **formatter_kw) # noqa: E501
axis.set_major_formatter(formatter)
def _update_limits(self, x, *, min_=None, max_=None, lim=None):
"""
Update the limits.
"""
# Try to use public API where possible
r = 'theta' if x == 'x' else 'r'
if lim is not None:
if min_ is not None or max_ is not None:
warnings._warn_proplot(
f'Overriding {r}min={min_} and {r}max={max_} '
f'with {r}lim={lim}.'
)
min_, max_ = lim
if min_ is not None:
getattr(self, 'set_' + r + 'min')(min_)
if max_ is not None:
getattr(self, 'set_' + r + 'max')(max_)
def _update_locators(
self, x, *,
locator=None, locator_kw=None, minorlocator=None, minorlocator_kw=None,
):
"""
Update the gridline locator.
"""
# TODO: Add minor tick 'toggling' as with cartesian axes?
# NOTE: Must convert theta locator input to radians, then back to deg.
r = 'theta' if x == 'x' else 'r'
axis = getattr(self, x + 'axis')
min_ = getattr(self, 'get_' + r + 'min')()
max_ = getattr(self, 'get_' + r + 'max')()
for i, (loc, loc_kw) in enumerate(
zip((locator, minorlocator), (locator_kw, minorlocator_kw))
):
if loc is None:
continue
# Get locator
loc_kw = loc_kw or {}
loc = constructor.Locator(loc, **loc_kw)
# Sanitize values
array = loc.tick_values(min_, max_)
array = array[(array >= min_) & (array <= max_)]
if x == 'x':
array = np.deg2rad(array)
if np.isclose(array[-1], min_ + 2 * np.pi): # exclusive if 360 deg
array = array[:-1]
# Assign fixed location
loc = constructor.Locator(array) # convert to FixedLocator
if i == 0:
axis.set_major_locator(loc)
else:
axis.set_minor_locator(loc)
[docs] @docstring.obfuscate_signature
@docstring.add_snippets
def format(
self, *, r0=None, theta0=None, thetadir=None,
thetamin=None, thetamax=None, thetalim=None,
rmin=None, rmax=None, rlim=None,
thetagrid=None, rgrid=None,
thetagridminor=None, rgridminor=None,
thetagridcolor=None, rgridcolor=None,
rlabelpos=None, rscale=None, rborder=None,
thetalocator=None, rlocator=None, thetalines=None, rlines=None,
thetalocator_kw=None, rlocator_kw=None,
thetaminorlocator=None, rminorlocator=None, thetaminorlines=None, rminorlines=None, # noqa: E501
thetaminorlocator_kw=None, rminorlocator_kw=None,
thetaformatter=None, rformatter=None, thetalabels=None, rlabels=None,
thetaformatter_kw=None, rformatter_kw=None, labelpad=None,
**kwargs
):
"""
Modify radial gridline locations, gridline labels, limits, and more.
Additional keyword argulents are passed to `Axes.format` and
`~proplot.config.Configurator.context`. Note that all ``theta``
arguments are specified in degrees, not radians.
Parameters
----------
r0 : float, optional
The radial origin. Default is ``0``.
theta0 : {'N', 'NW', 'W', 'SW', 'S', 'SE', 'E', 'NE'}
The zero azimuth location. Default is ``N``.
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
Whether to draw the polar axes border. Visibility of the "inner"
radial spine and "start" and "end" azimuthal spines is controlled
automatically by matplotlib.
thetagrid, rgrid, grid : bool, optional
Whether to draw major gridlines for the azimuthal and radial axis.
Use `grid` to toggle both.
thetagridminor, rgridminor, gridminor : bool, optional
Whether to draw minor gridlines for the azimuthal and radial axis.
Use `gridminor` to toggle both.
thetagridcolor, rgridcolor, gridcolor : color-spec, optional
Color for the major and minor azimuthal and radial gridlines.
Use `gridcolor` to set both at once.
thetalocator, rlocator : locator spec, 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`.
thetaminorlocator, rminorlocator : optional
As for `thetalocator`, `rlocator`, but for the minor gridlines.
thetaminorticks, rminorticks : optional
Aliases for `thetaminorlocator`, `rminorlocator`.
thetaminorlocator_kw, rminorlocator_kw
As for `thetalocator_kw`, `rlocator_kw`, but for the minor 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 ``[]``, ``'null'``, or ``'none'`` for no labels.
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`.
labelpad : float or str, optional
The padding between the axes edge and the radial and azimuthal
labels. Default is :rcraw:`grid.labelpad`.
%(units.pt)s
Other parameters
----------------
%(axes.format)s
%(figure.format)s
%(axes.rc)s
See also
--------
proplot.axes.Axes.format
proplot.config.Configurator.context
"""
# NOTE: Here we capture 'label.pad' rc argument normally used for
# x and y axis labels as shorthand for 'tick.labelpad'.
rc_kw, rc_mode, kwargs = _parse_format(**kwargs)
with rc.context(rc_kw, mode=rc_mode):
# Not mutable default args
thetalocator_kw = thetalocator_kw or {}
thetaminorlocator_kw = thetaminorlocator_kw or {}
thetaformatter_kw = thetaformatter_kw or {}
rlocator_kw = rlocator_kw or {}
rminorlocator_kw = rminorlocator_kw or {}
rformatter_kw = rformatter_kw or {}
# Flexible input
thetalocator = _not_none(thetalines=thetalines, thetalocator=thetalocator)
thetaformatter = _not_none(thetalabels=thetalabels, thetaformatter=thetaformatter) # noqa: E501
thetaminorlocator = _not_none(thetaminorlines=thetaminorlines, thetaminorlocator=thetaminorlocator) # noqa: E501
rlocator = _not_none(rlines=rlines, rlocator=rlocator)
rformatter = _not_none(rlabels=rlabels, rformatter=rformatter)
rminorlocator = _not_none(rminorlines=rminorlines, rminorlocator=rminorlocator) # noqa: E501
# 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)
# Loop over axes
for (
x,
grid,
gridminor,
gridcolor,
min_, max_, lim,
locator, locator_kw,
formatter, formatter_kw,
minorlocator, minorlocator_kw,
) in zip(
('x', 'y'),
(thetagrid, rgrid),
(thetagridminor, rgridminor),
(thetagridcolor, rgridcolor),
(thetamin, rmin), (thetamax, rmax), (thetalim, rlim),
(thetalocator, rlocator), (thetalocator_kw, rlocator_kw),
(thetaformatter, rformatter), (thetaformatter_kw, rformatter_kw),
(thetaminorlocator, rminorlocator),
(thetaminorlocator_kw, rminorlocator_kw),
):
# Axis limits
self._update_limits(x, min_=min_, max_=max_, lim=lim)
# Axis tick settings
# NOTE: Here use 'grid.labelpad' instead of 'tick.labelpad'. Default
# offset for grid labels is larger than for tick labels.
self._update_ticks(
x, grid=grid, gridminor=gridminor, gridcolor=gridcolor,
labelpad=labelpad, gridpad=True # use grid.labelpad
)
# Axis locator
self._update_locators(
x, locator=locator, locator_kw=locator_kw,
minorlocator=minorlocator, minorlocator_kw=minorlocator_kw
)
# Axis formatter
self._update_formatter(
x, formatter=formatter, formatter_kw=formatter_kw
)
# Parent format method
super().format(rc_kw=rc_kw, rc_mode=rc_mode, **kwargs)