Source code for proplot.ticker

#!/usr/bin/env python3
"""
Various `~matplotlib.ticker.Locator` and `~matplotlib.ticker.Formatter` classes.
"""
import locale
import re
from fractions import Fraction

import matplotlib.axis as maxis
import matplotlib.ticker as mticker
import numpy as np

from .config import rc
from .internals import ic  # noqa: F401
from .internals import _not_none, context, docstring

try:
    import cartopy.crs as ccrs
    from cartopy.mpl.ticker import (
        LatitudeFormatter,
        LongitudeFormatter,
        _PlateCarreeFormatter,
    )
except ModuleNotFoundError:
    ccrs = None
    LatitudeFormatter = LongitudeFormatter = _PlateCarreeFormatter = object

__all__ = [
    'IndexLocator',
    'DiscreteLocator',
    'DegreeLocator',
    'LongitudeLocator',
    'LatitudeLocator',
    'AutoFormatter',
    'SimpleFormatter',
    'IndexFormatter',
    'SciFormatter',
    'SigFigFormatter',
    'FracFormatter',
    'DegreeFormatter',
    'LongitudeFormatter',
    'LatitudeFormatter',
]

REGEX_ZERO = re.compile('\\A[-\N{MINUS SIGN}]?0(.0*)?\\Z')
REGEX_MINUS = re.compile('\\A[-\N{MINUS SIGN}]\\Z')
REGEX_MINUS_ZERO = re.compile('\\A[-\N{MINUS SIGN}]0(.0*)?\\Z')

_precision_docstring = """
precision : int, default: {6, 2}
    The maximum number of digits after the decimal point. Default is ``6``
    when `zerotrim` is ``True`` and ``2`` otherwise.
"""
_zerotrim_docstring = """
zerotrim : bool, default: :rc:`format.zerotrim`
    Whether to trim trailing decimal zeros.
"""
_auto_docstring = """
tickrange : 2-tuple of float, optional
    Range within which major tick marks are labeled.
    All ticks are labeled by default.
wraprange : 2-tuple of float, optional
    Range outside of which tick values are wrapped. For example,
    ``(-180, 180)`` will format a value of ``200`` as ``-160``.
prefix, suffix : str, optional
    Prefix and suffix for all tick strings. The suffix is added before
    the optional `negpos` suffix.
negpos : str, optional
    Length-2 string indicating the suffix for "negative" and "positive"
    numbers, meant to replace the minus sign.
"""
_formatter_call = """
Convert number to a string.

Parameters
----------
x : float
    The value.
pos : float, optional
    The position.
"""
docstring._snippet_manager['ticker.precision'] = _precision_docstring
docstring._snippet_manager['ticker.zerotrim'] = _zerotrim_docstring
docstring._snippet_manager['ticker.auto'] = _auto_docstring
docstring._snippet_manager['ticker.call'] = _formatter_call

_dms_docstring = """
Parameters
----------
dms : bool, default: False
    Locate the ticks on clean degree-minute-second intervals and format the
    ticks with minutes and seconds instead of decimals.
"""
docstring._snippet_manager['ticker.dms'] = _dms_docstring


def _default_precision_zerotrim(precision=None, zerotrim=None):
    """
    Return the default zerotrim and precision. Shared by several formatters.
    """
    zerotrim = _not_none(zerotrim, rc['formatter.zerotrim'])
    if precision is None:
        precision = 6 if zerotrim else 2
    return precision, zerotrim


[docs]class IndexLocator(mticker.Locator): """ Format numbers by assigning fixed strings to non-negative indices. The ticks are restricted to the extent of plotted content when content is present. """ def __init__(self, base=1, offset=0): self._base = base self._offset = offset def set_params(self, base=None, offset=None): if base is not None: self._base = base if offset is not None: self._offset = offset def __call__(self): # NOTE: We adapt matplotlib IndexLocator to support case where # the data interval is empty. Only restrict after data is plotted. dmin, dmax = self.axis.get_data_interval() vmin, vmax = self.axis.get_view_interval() min_ = max(dmin, vmin) max_ = min(dmax, vmax) return self.tick_values(min_, max_) def tick_values(self, vmin, vmax): base, offset = self._base, self._offset vmin = max(base * np.ceil(vmin / base), offset) vmax = max(base * np.floor(vmax / base), offset) locs = np.arange(vmin, vmax + 0.5 * base, base) return self.raise_if_exceeds(locs)
[docs]class DiscreteLocator(mticker.Locator): """ A tick locator suitable for discretized colorbars. Adds ticks to some subset of the location list depending on the available space determined from `~matplotlib.axis.Axis.get_tick_space`. Zero will be used if it appears in the location list, and step sizes along the location list are restricted to "nice" intervals by default. """ default_params = { 'nbins': None, 'minor': False, 'steps': np.array([1, 2, 3, 4, 5, 6, 8, 10]), 'vcenter': 0.0, 'min_n_ticks': 2 } @docstring._snippet_manager def __init__(self, locs, **kwargs): """ Parameters ---------- locs : array-like The tick location list. nbins : int, optional Maximum number of ticks to select. By default this is automatically determined based on the the axis length and tick label font size. minor : bool, default: False Whether this is for "minor" ticks. Setting to ``True`` will select more ticks with an index step that divides the index step used for "major" ticks. steps : array-like of int, default: ``[1 2 3 4 5 6 8]`` Valid integer index steps when selecting from the tick list. Must fall between 1 and 9. Powers of 10 of these step sizes will also be permitted. vcenter : float, optional The optional non-zero center of the original diverging normalizer. min_n_ticks : int, default: 1 The minimum number of ticks to select. See also `nbins`. """ self.locs = np.array(locs) self._nbins = None # otherwise unset self.set_params(**{**self.default_params, **kwargs})
[docs] def __call__(self): """ Return the locations of the ticks. """ return self.tick_values(None, None)
[docs] def set_params(self, nbins=None, minor=None, steps=None, vcenter=None, min_n_ticks=None): # noqa: E501 """ Set the parameters for this locator. See `DiscreteLocator` for details. """ if steps is not None: steps = np.unique(np.array(steps, dtype=int)) # also sorts, makes 1D if np.any(steps < 1) or np.any(steps > 10): raise ValueError('Steps must fall between one and ten (inclusive).') if steps[0] != 1: steps = np.concatenate([[1], steps]) if steps[-1] != 10: steps = np.concatenate([steps, [10]]) self._steps = steps if nbins is not None: self._nbins = nbins if minor is not None: self._minor = bool(minor) # needed to scale tick space if vcenter is not None: self._vcenter = vcenter if min_n_ticks is not None: self._min_n_ticks = int(min_n_ticks) # compare to MaxNLocator
[docs] def tick_values(self, vmin, vmax): # noqa: U100 """ Return the locations of the ticks. """ # NOTE: Critical that minor tick interval evenly divides major tick # interval. Otherwise get misaligned major and minor tick steps. # NOTE: This tries to select ticks that are integer steps away from zero (like # AutoLocator). The list minimum is used if this fails (like FixedLocator) # NOTE: This avoids awkward steps like '7' or '13' that produce strange # jumps and have no integer divisors (and therefore eliminate minor ticks) # NOTE: We virtually always want to subsample the level list rather than # using continuous minor locators (e.g. LogLocator or SymLogLocator) because # _parse_autolev interpolates evenly in the norm-space (e.g. 1, 3.16, 10, 31.6 # for a LogNorm) rather than in linear-space (e.g. 1, 5, 10, 15, 20). locs = self.locs axis = self.axis if axis is None: return locs nbins = self._nbins steps = self._steps if nbins is None: nbins = axis.get_tick_space() nbins = max((1, self._min_n_ticks - 1, nbins)) step = max(1, int(np.ceil(locs.size / nbins))) fact = 10 ** max(0, -AutoFormatter._decimal_place(step)) # e.g. 2 for 100 idx = min(len(steps) - 1, np.searchsorted(steps, step / fact)) step = int(np.round(steps[idx] * fact)) if self._minor: # tick every half font size if isinstance(axis, maxis.XAxis): fact = 6 # unscale heuristic scaling of 3 em-widths elif isinstance(axis, maxis.YAxis): fact = 4 # unscale standard scaling of 2 em-widths else: fact = 2 # fall back to just one em-width for i in range(fact, 0, -1): if step % i == 0: step = step // i break locs = locs - self._vcenter diff = np.abs(np.diff(locs[:step + 1:step])) offset, = np.where(np.isclose(locs % diff if diff.size else 0.0, 0.0)) offset = offset[0] if offset.size else np.argmin(np.abs(locs)) locs = locs[offset % step::step] # even multiples from zero or zero-close return locs + self._vcenter
[docs]class DegreeLocator(mticker.MaxNLocator): """ Locate geographic gridlines with degree-minute-second support. Adapted from cartopy. """ # NOTE: This is identical to cartopy except they only define LongitutdeLocator # for common methods whereas we use DegreeLocator. More intuitive this way in # case users need degree-minute-seconds for non-specific degree axis. # NOTE: Locator implementation is weird AF. __init__ just calls set_params with all # keyword args and fills in missing params with default_params class attribute. # Unknown params result in warning instead of error. default_params = mticker.MaxNLocator.default_params.copy() default_params.update(nbins=8, dms=False) @docstring._snippet_manager def __init__(self, *args, **kwargs): """ %(ticker.dms)s """ super().__init__(*args, **kwargs) def set_params(self, **kwargs): if 'dms' in kwargs: self._dms = kwargs.pop('dms') super().set_params(**kwargs) def _guess_steps(self, vmin, vmax): dv = abs(vmax - vmin) if dv > 180: dv -= 180 if dv > 50: steps = np.array([1, 2, 3, 6, 10]) elif not self._dms or dv > 3.0: steps = np.array([1, 1.5, 2, 2.5, 3, 5, 10]) else: steps = np.array([1, 10 / 6.0, 15 / 6.0, 20 / 6.0, 30 / 6.0, 10]) self.set_params(steps=np.array(steps)) def _raw_ticks(self, vmin, vmax): self._guess_steps(vmin, vmax) return super()._raw_ticks(vmin, vmax) def bin_boundaries(self, vmin, vmax): # matplotlib < 2.2.0 return self._raw_ticks(vmin, vmax) # may call Latitude/Longitude Locator copies
[docs]class LongitudeLocator(DegreeLocator): """ Locate longitude gridlines with degree-minute-second support. Adapted from cartopy. """ @docstring._snippet_manager def __init__(self, *args, **kwargs): """ %(ticker.dms)s """ super().__init__(*args, **kwargs)
[docs]class LatitudeLocator(DegreeLocator): """ Locate latitude gridlines with degree-minute-second support. Adapted from cartopy. """ @docstring._snippet_manager def __init__(self, *args, **kwargs): """ %(ticker.dms)s """ super().__init__(*args, **kwargs) def tick_values(self, vmin, vmax): vmin = max(vmin, -90) vmax = min(vmax, 90) return super().tick_values(vmin, vmax) def _guess_steps(self, vmin, vmax): vmin = max(vmin, -90) vmax = min(vmax, 90) super()._guess_steps(vmin, vmax) def _raw_ticks(self, vmin, vmax): ticks = super()._raw_ticks(vmin, vmax) return [t for t in ticks if -90 <= t <= 90]
[docs]class AutoFormatter(mticker.ScalarFormatter): """ The default formatter used for proplot tick labels. Replaces `~matplotlib.ticker.ScalarFormatter`. """ @docstring._snippet_manager def __init__( self, zerotrim=None, tickrange=None, wraprange=None, prefix=None, suffix=None, negpos=None, **kwargs ): """ Parameters ---------- %(ticker.zerotrim)s %(ticker.auto)s Other parameters ---------------- **kwargs Passed to `matplotlib.ticker.ScalarFormatter`. See also -------- proplot.constructor.Formatter proplot.ticker.SimpleFormatter Note ---- `matplotlib.ticker.ScalarFormatter` determines the number of significant digits based on the axis limits, and therefore may truncate digits while formatting ticks on highly non-linear axis scales like `~proplot.scale.LogScale`. `AutoFormatter` corrects this behavior, making it suitable for arbitrary axis scales. We therefore use `AutoFormatter` with every axis scale by default. """ tickrange = tickrange or (-np.inf, np.inf) super().__init__(**kwargs) zerotrim = _not_none(zerotrim, rc['formatter.zerotrim']) self._zerotrim = zerotrim self._tickrange = tickrange self._wraprange = wraprange self._prefix = prefix or '' self._suffix = suffix or '' self._negpos = negpos or ''
[docs] @docstring._snippet_manager def __call__(self, x, pos=None): """ %(ticker.call)s """ # Tick range limitation x = self._wrap_tick_range(x, self._wraprange) if self._outside_tick_range(x, self._tickrange): return '' # Negative positive handling x, tail = self._neg_pos_format(x, self._negpos, wraprange=self._wraprange) # Default string formatting string = super().__call__(x, pos) # Fix issue where non-zero string is formatted as zero string = self._fix_small_number(x, string) # Custom string formatting string = self._minus_format(string) if self._zerotrim: string = self._trim_trailing_zeros(string, self._get_decimal_point()) # Prefix and suffix string = self._add_prefix_suffix(string, self._prefix, self._suffix) string = string + tail # add negative-positive indicator return string
[docs] def get_offset(self): """ Get the offset but *always* use math text. """ with context._state_context(self, _useMathText=True): return super().get_offset()
@staticmethod def _add_prefix_suffix(string, prefix=None, suffix=None): """ Add prefix and suffix to string. """ sign = '' prefix = prefix or '' suffix = suffix or '' if string and REGEX_MINUS.match(string[0]): sign, string = string[0], string[1:] return sign + prefix + string + suffix def _fix_small_number(self, x, string, precision_offset=2): """ Fix formatting for non-zero formatted as zero. The `offset` controls the offset from true floating point precision at which we want to limit string precision. """ # Add just enough precision for small numbers. Default formatter is # only meant to be used for linear scales and cannot handle the wide # range of magnitudes in e.g. log scales. To correct this, we only # truncate if value is within `offset` order of magnitude of the float # precision. Common issue is e.g. levels=pplt.arange(-1, 1, 0.1). # This choice satisfies even 1000 additions of 0.1 to -100. m = REGEX_ZERO.match(string) decimal_point = self._get_decimal_point() if m and x != 0: # Get initial precision spit out by algorithm decimals, = m.groups() precision_init = len(decimals.lstrip(decimal_point)) if decimals else 0 # Format with precision below floating point error x -= getattr(self, 'offset', 0) # guard against API change x /= 10 ** getattr(self, 'orderOfMagnitude', 0) # guard against API change precision_true = max(0, self._decimal_place(x)) precision_max = max(0, np.finfo(type(x)).precision - precision_offset) precision = min(precision_true, precision_max) string = ('{:.%df}' % precision).format(x) # If zero ignoring floating point error then match original precision if REGEX_ZERO.match(string): string = ('{:.%df}' % precision_init).format(0) # Fix decimal point string = string.replace('.', decimal_point) return string def _get_decimal_point(self, use_locale=None): """ Get decimal point symbol for current locale (e.g. in Europe will be comma). """ use_locale = _not_none(use_locale, self.get_useLocale()) return self._get_default_decimal_point(use_locale) @staticmethod def _get_default_decimal_point(use_locale=None): """ Get decimal point symbol for current locale. Called externally. """ use_locale = _not_none(use_locale, rc['formatter.use_locale']) return locale.localeconv()['decimal_point'] if use_locale else '.' @staticmethod def _decimal_place(x): """ Return the decimal place of the number (e.g., 100 is -2 and 0.01 is 2). """ if x == 0: digits = 0 else: digits = -int(np.log10(abs(x)) // 1) return digits @staticmethod def _minus_format(string): """ Format the minus sign and avoid "negative zero," e.g. ``-0.000``. """ if rc['axes.unicode_minus'] and not rc['text.usetex']: string = string.replace('-', '\N{MINUS SIGN}') if REGEX_MINUS_ZERO.match(string): string = string[1:] return string @staticmethod def _neg_pos_format(x, negpos, wraprange=None): """ Permit suffixes indicators for "negative" and "positive" numbers. """ # NOTE: If input is a symmetric wraprange, the value conceptually has # no "sign", so trim tail and format as absolute value. if not negpos or x == 0: tail = '' elif ( wraprange is not None and np.isclose(-wraprange[0], wraprange[1]) and np.any(np.isclose(x, wraprange)) ): x = abs(x) tail = '' elif x > 0: tail = negpos[1] else: x *= -1 tail = negpos[0] return x, tail @staticmethod def _outside_tick_range(x, tickrange): """ Return whether point is outside tick range up to some precision. """ eps = abs(x) / 1000 return (x + eps) < tickrange[0] or (x - eps) > tickrange[1] @staticmethod def _trim_trailing_zeros(string, decimal_point='.'): """ Sanitize tick label strings. """ if decimal_point in string: string = string.rstrip('0').rstrip(decimal_point) return string @staticmethod def _wrap_tick_range(x, wraprange): """ Wrap the tick range to within these values. """ if wraprange is None: return x base = wraprange[0] modulus = wraprange[1] - wraprange[0] return (x - base) % modulus + base
[docs]class SimpleFormatter(mticker.Formatter): """ A general purpose number formatter. This is similar to `AutoFormatter` but suitable for arbitrary formatting not necessarily associated with an `~matplotlib.axis.Axis` instance. """ @docstring._snippet_manager def __init__( self, precision=None, zerotrim=None, tickrange=None, wraprange=None, prefix=None, suffix=None, negpos=None, ): """ Parameters ---------- %(ticker.precision)s %(ticker.zerotrim)s %(ticker.auto)s See also -------- proplot.constructor.Formatter proplot.ticker.AutoFormatter """ precision, zerotrim = _default_precision_zerotrim(precision, zerotrim) self._precision = precision self._prefix = prefix or '' self._suffix = suffix or '' self._negpos = negpos or '' self._tickrange = tickrange or (-np.inf, np.inf) self._wraprange = wraprange self._zerotrim = zerotrim
[docs] @docstring._snippet_manager def __call__(self, x, pos=None): # noqa: U100 """ %(ticker.call)s """ # Tick range limitation x = AutoFormatter._wrap_tick_range(x, self._wraprange) if AutoFormatter._outside_tick_range(x, self._tickrange): return '' # Negative positive handling x, tail = AutoFormatter._neg_pos_format( x, self._negpos, wraprange=self._wraprange ) # Default string formatting decimal_point = AutoFormatter._get_default_decimal_point() string = ('{:.%df}' % self._precision).format(x) string = string.replace('.', decimal_point) # Custom string formatting string = AutoFormatter._minus_format(string) if self._zerotrim: string = AutoFormatter._trim_trailing_zeros(string, decimal_point) # Prefix and suffix string = AutoFormatter._add_prefix_suffix(string, self._prefix, self._suffix) string = string + tail # add negative-positive indicator return string
[docs]class IndexFormatter(mticker.Formatter): """ Format numbers by assigning fixed strings to non-negative indices. Generally paired with `IndexLocator` or `~matplotlib.ticker.FixedLocator`. """ # NOTE: This was deprecated in matplotlib 3.3. For details check out # https://github.com/matplotlib/matplotlib/issues/16631 and bring some popcorn. def __init__(self, labels): self.labels = labels self.n = len(labels) def __call__(self, x, pos=None): # noqa: U100 i = int(round(x)) if i < 0 or i >= self.n: return '' else: return self.labels[i]
[docs]class SciFormatter(mticker.Formatter): """ Format numbers with scientific notation. """ @docstring._snippet_manager def __init__(self, precision=None, zerotrim=None): """ Parameters ---------- %(ticker.precision)s %(ticker.zerotrim)s See also -------- proplot.constructor.Formatter proplot.ticker.AutoFormatter """ precision, zerotrim = _default_precision_zerotrim(precision, zerotrim) self._precision = precision self._zerotrim = zerotrim
[docs] @docstring._snippet_manager def __call__(self, x, pos=None): # noqa: U100 """ %(ticker.call)s """ # Get string decimal_point = AutoFormatter._get_default_decimal_point() string = ('{:.%de}' % self._precision).format(x) parts = string.split('e') # Trim trailing zeros significand = parts[0].rstrip(decimal_point) if self._zerotrim: significand = AutoFormatter._trim_trailing_zeros(significand, decimal_point) # Get sign and exponent sign = parts[1][0].replace('+', '') exponent = parts[1][1:].lstrip('0') if exponent: exponent = f'10^{{{sign}{exponent}}}' if significand and exponent: string = rf'{significand}{{\times}}{exponent}' else: string = rf'{significand}{exponent}' # Ensure unicode minus sign string = AutoFormatter._minus_format(string) # Return TeX string return f'${string}$'
[docs]class SigFigFormatter(mticker.Formatter): """ Format numbers by retaining the specified number of significant digits. """ @docstring._snippet_manager def __init__(self, sigfig=None, zerotrim=None, base=None): """ Parameters ---------- sigfig : float, default: 3 The number of significant digits. %(ticker.zerotrim)s base : float, default: 1 The base unit for rounding. For example ``SigFigFormatter(2, base=5)`` rounds to the nearest 5 with up to 2 digits (e.g., 87 --> 85, 8.7 --> 8.5). See also -------- proplot.constructor.Formatter proplot.ticker.AutoFormatter """ self._sigfig = _not_none(sigfig, 3) self._zerotrim = _not_none(zerotrim, rc['formatter.zerotrim']) self._base = _not_none(base, 1)
[docs] @docstring._snippet_manager def __call__(self, x, pos=None): # noqa: U100 """ %(ticker.call)s """ # Limit to significant figures digits = AutoFormatter._decimal_place(x) + self._sigfig - 1 scale = self._base * 10 ** -digits x = scale * round(x / scale) # Create the string decimal_point = AutoFormatter._get_default_decimal_point() precision = max(0, digits) + max(0, AutoFormatter._decimal_place(self._base)) string = ('{:.%df}' % precision).format(x) string = string.replace('.', decimal_point) # Custom string formatting string = AutoFormatter._minus_format(string) if self._zerotrim: string = AutoFormatter._trim_trailing_zeros(string, decimal_point) return string
[docs]class FracFormatter(mticker.Formatter): r""" Format numbers as integers or integer fractions. Optionally express the values relative to some constant like `numpy.pi`. """ def __init__(self, symbol='', number=1): r""" Parameters ---------- symbol : str, default: '' The constant symbol, e.g. ``r'$\pi$'``. number : float, default: 1 The constant value, e.g. `numpy.pi`. Note ---- The fractions shown by this formatter are resolved using the builtin `fractions.Fraction` class and `fractions.Fraction.limit_denominator`. See also -------- proplot.constructor.Formatter proplot.ticker.AutoFormatter """ self._symbol = symbol self._number = number super().__init__()
[docs] @docstring._snippet_manager def __call__(self, x, pos=None): # noqa: U100 """ %(ticker.call)s """ frac = Fraction(x / self._number).limit_denominator() symbol = self._symbol if x == 0: string = '0' elif frac.denominator == 1: # denominator is one if frac.numerator == 1 and symbol: string = f'{symbol:s}' elif frac.numerator == -1 and symbol: string = f'-{symbol:s}' else: string = f'{frac.numerator:d}{symbol:s}' else: if frac.numerator == 1 and symbol: # numerator is +/-1 string = f'{symbol:s}/{frac.denominator:d}' elif frac.numerator == -1 and symbol: string = f'-{symbol:s}/{frac.denominator:d}' else: # and again make sure we use unicode minus! string = f'{frac.numerator:d}{symbol:s}/{frac.denominator:d}' string = AutoFormatter._minus_format(string) return string
class _CartopyFormatter(object): """ Mixin class for cartopy formatters. """ # NOTE: Cartopy formatters pre 0.18 required axis, and *always* translated # input values from map projection coordinates to Plate Carrée coordinates. # After 0.18 you can avoid this behavior by not setting axis but really # dislike that inconsistency. Solution is temporarily assign PlateCarre(). def __init__(self, *args, **kwargs): import cartopy # noqa: F401 (ensure available) super().__init__(*args, **kwargs) def __call__(self, value, pos=None): ctx = context._empty_context() if self.axis is not None: ctx = context._state_context(self.axis.axes, projection=ccrs.PlateCarree()) with ctx: return super().__call__(value, pos)
[docs]class DegreeFormatter(_CartopyFormatter, _PlateCarreeFormatter): """ Formatter for longitude and latitude gridline labels. Adapted from cartopy. """ @docstring._snippet_manager def __init__(self, *args, **kwargs): """ %(ticker.dms)s """ super().__init__(*args, **kwargs) def _apply_transform(self, value, *args, **kwargs): # noqa: U100 return value def _hemisphere(self, value, *args, **kwargs): # noqa: U100 return ''
[docs]class LongitudeFormatter(_CartopyFormatter, LongitudeFormatter): """ Format longitude gridline labels. Adapted from `cartopy.mpl.ticker.LongitudeFormatter`. """ @docstring._snippet_manager def __init__(self, *args, **kwargs): """ %(ticker.dms)s """ super().__init__(*args, **kwargs)
[docs]class LatitudeFormatter(_CartopyFormatter, LatitudeFormatter): """ Format latitude gridline labels. Adapted from `cartopy.mpl.ticker.LatitudeFormatter`. """ @docstring._snippet_manager def __init__(self, *args, **kwargs): """ %(ticker.dms)s """ super().__init__(*args, **kwargs)