Skip to content

Commit

Permalink
Use AutoFormatter for deglat/lon/etc., improve AutoFormatter log-scal…
Browse files Browse the repository at this point in the history
…e patch
  • Loading branch information
lukelbd committed Dec 9, 2019
1 parent 9b16473 commit 8520e36
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 67 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -60,6 +60,24 @@ ProPlot v0.3.0 (2020-01-##)
with a default ``.proplotrc`` file, change the auto-generated user ``.proplotrc``
(:pr:`50`).

ProPlot v0.2.6 (2019-12-09)
===========================

.. rubric:: Bug fixes

- Fix issue where `~proplot.styletools.AutoFormatter` logarithmic scale
points are incorrect (:commit:`9b164733`).

.. rubric:: Internals

- Remove `prefix`, `suffix`, and `negpos` keyword args from
`~proplot.styletools.SimpleFormatter`, remove `precision` keyword arg from
`~proplot.styletools.AutoFormatter` (it automatically figures out the
necessary precision!).
- Make ``'deglat'``, ``'deglon'``, ``'lat'``, ``'lon'``, and ``'deg'`` instances
of `~proplot.styletools.AutoFormatter` instead of `~proplot.styletools.SimpleFormatter`.
The latter should just be used for contours.

ProPlot v0.2.6 (2019-12-08)
===========================
.. rubric:: Bug fixes
Expand Down
24 changes: 12 additions & 12 deletions docs/axis.ipynb
Expand Up @@ -84,6 +84,15 @@
"ProPlot lets you easily change the axis formatter with `~proplot.axes.Axes.format` (keyword args `xformatter` and `yformatter`, or their aliases `xticklabels` and `yticklabels`). The builtin matplotlib formatters can be referenced by string name, and several new formatters have been introduced -- for example, you can now easily label your axes as fractions or as geographic coordinates. You can also just pass a list of strings or a ``%`` style format directive. See `~proplot.axes.XYAxes.format` and `~proplot.axistools.Formatter` for details."
]
},
{
"cell_type": "raw",
"metadata": {
"raw_mimetype": "text/restructuredtext"
},
"source": [
"ProPlot also changes the default axis formatter. The new formatter trims trailing zeros by default, and can be used to *filter tick labels within some data range*, as demonstrated below. See `~proplot.axistools.AutoFormatter` for details."
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -109,15 +118,6 @@
"plot.rc.reset()"
]
},
{
"cell_type": "raw",
"metadata": {
"raw_mimetype": "text/restructuredtext"
},
"source": [
"ProPlot also changes the default axis formatter. The new formatter trims trailing zeros by default, and can be used to *filter tick labels within some data range*, as demonstrated below. See `~proplot.axistools.AutoFormatter` for details."
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -155,8 +155,8 @@
"raw_mimetype": "text/restructuredtext"
},
"source": [
"The axis scale can now be changed with `~proplot.axes.Axes.format` (keyword args `xscale` and `yscale`). You can also configure the ``'log'`` and ``'symlog'`` axis scales with the more sensible `base`, `linthresh`, `linscale`, and `subs`\n",
"keyword args (i.e. you can omit the ``x`` and ``y``). See `~proplot.axes.XYAxes.format`, `~proplot.axistools.Scale`, `~proplot.axistools.LogScale` and `~proplot.axistools.SymmetricalLogScale` for details."
"The axis scale can now be changed with `~proplot.axes.Axes.format` (keyword args `xscale` and `yscale`). You can now configure the ``'log'`` and ``'symlog'`` axis scales with the more sensible `base`, `linthresh`, `linscale`, and `subs`\n",
"keyword args, rather than ``basex``, ``basey``, etc. Also, ProPlot's `~proplot.axistools.AutoFormatter` formatter is used for all axis scales by default; this can be changed e.g. by passing ``yformatter='log'`` to `~proplot.axes.XYAxes.format`. See `~proplot.axistools.Scale`, `~proplot.axistools.LogScale` and `~proplot.axistools.SymmetricalLogScale` for details."
]
},
{
Expand Down Expand Up @@ -201,7 +201,7 @@
"raw_mimetype": "text/restructuredtext"
},
"source": [
"ProPlot also adds several new axis scales. The ``'cutoff'`` scale is great when you have weirdly distributed data (see `~proplot.axistools.CutoffScale`). The ``'sine'`` scale scales the axis as the sine of the coordinate, resulting in an \"area-weighted\" spherical latitude coordinate, and the ``'Mercator'`` scale scales the axis as with the Mercator projection latitude coordinate. The ``'inverse'`` scale is perfect for working with spectral data (this is more useful with `~proplot.axes.XYAxes.dualx` and `~proplot.axes.XYAxes.dualy`; see :ref:`Dual unit axes`)."
"ProPlot adds several new axis scales. The ``'cutoff'`` scale is great when you have weirdly distributed data (see `~proplot.axistools.CutoffScale`). The ``'sine'`` scale scales the axis as the sine of the coordinate, resulting in an \"area-weighted\" spherical latitude coordinate, and the ``'Mercator'`` scale scales the axis as with the Mercator projection latitude coordinate. The ``'inverse'`` scale is perfect for working with spectral data (this is more useful with `~proplot.axes.XYAxes.dualx` and `~proplot.axes.XYAxes.dualy`; see :ref:`Dual unit axes`)."
]
},
{
Expand Down
116 changes: 61 additions & 55 deletions proplot/axistools.py
Expand Up @@ -30,7 +30,7 @@
'SymmetricalLogScale',
]

# Scale preset names and positional args
MAX_DIGITS = 32 # do not draw 1000 digits when LogScale limits include zero!
SCALE_PRESETS = {
'quadratic': ('power', 2,),
'cubic': ('power', 3,),
Expand Down Expand Up @@ -194,11 +194,11 @@ def Formatter(formatter, *args, date=False, index=False, **kwargs):
``'theta'`` `~matplotlib.projections.polar.ThetaFormatter` Formats radians as degrees, with a degree symbol
``'pi'`` `FracFormatter` preset Fractions of :math:`\\pi`
``'e'`` `FracFormatter` preset Fractions of *e*
``'deg'`` `SimpleFormatter` preset Trailing degree symbol
``'deglon'`` `SimpleFormatter` preset Trailing degree symbol and cardinal "WE" indicator
``'deglat'`` `SimpleFormatter` preset Trailing degree symbol and cardinal "SN" indicator
``'lon'`` `SimpleFormatter` preset Cardinal "WE" indicator
``'lat'`` `SimpleFormatter` preset Cardinal "SN" indicator
``'deg'`` `AutoFormatter` preset Trailing degree symbol
``'deglon'`` `AutoFormatter` preset Trailing degree symbol and cardinal "WE" indicator
``'deglat'`` `AutoFormatter` preset Trailing degree symbol and cardinal "SN" indicator
``'lon'`` `AutoFormatter` preset Cardinal "WE" indicator
``'lat'`` `AutoFormatter` preset Cardinal "SN" indicator
====================== ============================================== ===================================================================================================================================
date : bool, optional
Expand Down Expand Up @@ -255,7 +255,7 @@ def Formatter(formatter, *args, date=False, index=False, **kwargs):
negpos = 'WE'
kwargs.setdefault('suffix', suffix)
kwargs.setdefault('negpos', negpos)
formatter = 'simple'
formatter = 'auto'
# Lookup
if formatter not in formatters:
raise ValueError(
Expand Down Expand Up @@ -348,6 +348,15 @@ def Scale(scale, *args, **kwargs):
return scale(*args, **kwargs)


def _zerofix(x, string, precision=6):
"""
Try to fix non-zero tick labels formatted as ``'0'``.
"""
if string.rstrip('0').rstrip('.') == '0' and x != 0:
string = ('{:.%df}' % precision).format(x)
return string


class AutoFormatter(mticker.ScalarFormatter):
"""
The new default formatter, a simple wrapper around
Expand All @@ -362,30 +371,40 @@ class AutoFormatter(mticker.ScalarFormatter):
"""
def __init__(self, *args,
zerotrim=None, precision=None, tickrange=None,
prefix=None, suffix=None, **kwargs):
prefix=None, suffix=None, negpos=None, **kwargs):
"""
Parameters
----------
zerotrim : bool, optional
Whether to trim trailing zeros.
Default is :rc:`axes.formatter.zerotrim`.
precision : float, optional
The maximum number of digits after the decimal point.
tickrange : (float, float), optional
Range within which major tick marks are labelled.
prefix, suffix : str, optional
Optional prefix and suffix for all strings.
Prefix and suffix for all strings.
negpos : str, optional
Length-2 string indicating the suffix for "negative" and "positive"
numbers, meant to replace the minus sign. This is useful for
indicating cardinal geographic coordinates.
*args, **kwargs
Passed to `matplotlib.ticker.ScalarFormatter`.
Passed to `~matplotlib.ticker.ScalarFormatter`.
Warning
-------
The matplotlib `~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.axistools.LogScale`. We try to correct
this behavior with a patch.
"""
tickrange = tickrange or (-np.inf, np.inf)
super().__init__(*args, **kwargs)
zerotrim = _notNone(zerotrim, rc.get('axes.formatter.zerotrim'))
self._maxprecision = precision
self._zerotrim = zerotrim
self._tickrange = tickrange
self._prefix = prefix or ''
self._suffix = suffix or ''
self._negpos = negpos or ''

def __call__(self, x, pos=None):
"""
Expand All @@ -403,75 +422,62 @@ def __call__(self, x, pos=None):
tickrange = self._tickrange
if (x + eps) < tickrange[0] or (x - eps) > tickrange[1]:
return '' # avoid some ticks
# Normal formatting
# Negative positive handling
if not self._negpos:
tail = ''
elif x > 0:
tail = self._negpos[1]
else:
x *= -1
tail = self._negpos[0]
# Format the string
string = super().__call__(x, pos)
if string == '0' and x != 0: # weird LogScale issue
string = ('{:.%df}' % (self._maxprecision or 6)).format(x)
if self._maxprecision is not None and '.' in string:
head, tail = string.split('.')
string = head + '.' + tail[:self._maxprecision]
if self._zerotrim and '.' in string:
string = string.rstrip('0').rstrip('.')
if string == '-0' or string == '\N{MINUS SIGN}0':
string = '0'
for i in range(2):
# Try to fix non-zero values formatted as zero
if self._zerotrim and '.' in string:
string = string.rstrip('0').rstrip('.')
if string == '-0' or string == '\N{MINUS SIGN}0':
string = '0'
if i == 0 and string == '0' and x != 0:
# Hard limit of 10 sigfigs
string = ('{:.%df}' % min(
abs(np.log10(x) // 1), MAX_DIGITS)).format(x)
continue
break
# Prefix and suffix
sign = ''
string = string.replace('-', '\N{MINUS SIGN}')
if string and string[0] == '\N{MINUS SIGN}':
sign, string = string[0], string[1:]
# if 0 < x < 1:
# print(x, repr(string))
return sign + self._prefix + string + self._suffix
if tail:
sign = ''
return sign + self._prefix + string + self._suffix + tail


def SimpleFormatter(*args, precision=6,
prefix=None, suffix=None, negpos=None, zerotrim=True,
**kwargs):
def SimpleFormatter(*args, precision=6, zerotrim=True, **kwargs):
"""
Replicates features of `AutoFormatter`, but as a simpler
Replicates the `zerotrim` feature from `AutoFormatter`, but as a simpler
`~matplotlib.ticker.FuncFormatter` instance. This is more suitable for
arbitrary number formatting not necessarily associated with any
`~matplotlib.axis.Axis` instance, e.g. labelling contours.
`~matplotlib.axis.Axis` instance, e.g. labeling contours.
Parameters
----------
precision : int, optional
Maximum number of digits after the decimal point.
prefix, suffix : str, optional
Optional prefix and suffix for all strings.
negpos : str, optional
Length-2 string that indicates suffix for "negative" and "positive"
numbers, meant to replace the minus sign. This is useful for
indicating cardinal geographic coordinates.
The maximum number of digits after the decimal point.
zerotrim : bool, optional
Whether to trim trailing zeros.
Default is :rc:`axes.formatter.zerotrim`.
"""
prefix = prefix or ''
suffix = suffix or ''
zerotrim = _notNone(zerotrim, rc['axes.formatter.zerotrim'])

def f(x, pos):
# Apply suffix if not on equator/prime meridian
if not negpos:
tail = ''
elif x > 0:
tail = negpos[1]
else:
x *= -1
tail = negpos[0]
# Finally use default formatter
string = ('{:.%df}' % precision).format(x)
if zerotrim and '.' in string:
string = string.rstrip('0').rstrip('.')
if string == '-0' or string == '\N{MINUS SIGN}0':
string = '0'
# Prefix and suffix
sign = ''
string = string.replace('-', '\N{MINUS SIGN}')
if string and string[0] == '\N{MINUS SIGN}':
sign, string = string[0], string[1:]
return sign + prefix + string + suffix + tail
return string.replace('-', '\N{MINUS SIGN}')
return mticker.FuncFormatter(f)


Expand Down

0 comments on commit 8520e36

Please sign in to comment.