From eeb76585b932027075d987b03bcba012f5699f8d Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Tue, 29 Mar 2016 16:17:42 -0400 Subject: [PATCH 1/7] MAINT: Updated mailmap --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 395e7c73ee0e..b9456a8ef436 100644 --- a/.mailmap +++ b/.mailmap @@ -28,6 +28,7 @@ John Hunter Jorrit Wronski Jouni K. Seppänen Joseph Fox-Rabinovitz Mad Physicist +Joseph Fox-Rabinovitz Joseph Fox-Rabinovitz Julien Schueller Julien Schueller Kevin Davies From 9ba1428d7772da82c8c88ee7900f93a6890f3b9b Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Tue, 29 Mar 2016 16:20:57 -0400 Subject: [PATCH 2/7] ENH: Added support for both x and pos to StrMethodFormatter This should not break backwards compatibility since extra keywords are allowed --- lib/matplotlib/ticker.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 342b962d6211..526c38ca269e 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -377,14 +377,22 @@ def __call__(self, x, pos=None): class StrMethodFormatter(Formatter): """ Use a new-style format string (as used by `str.format()`) - to format the tick. The field formatting must be labeled `x`. + to format the tick. + + The field used for the value must be labeled `x` and the field used + for the position must be labeled `pos`. """ def __init__(self, fmt): self.fmt = fmt def __call__(self, x, pos=None): - 'Return the format for tick val *x* at position *pos*' - return self.fmt.format(x=x) + """ + Return the formatted label string. + + `x` and `pos` are passed to `str.format` as keyword arguments + with those exact names. + """ + return self.fmt.format(x=x, pos=pos) class OldScalarFormatter(Formatter): From 9cb7ee8ce880db604ca22417185ddfadc98e54e3 Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Tue, 29 Mar 2016 21:54:51 -0400 Subject: [PATCH 3/7] TST: Test for new pos format --- lib/matplotlib/tests/test_ticker.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 0598396e385c..413286b3b47a 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -371,6 +371,10 @@ def _percent_format_helper(xmax, decimals, symbol, x, display_range, expected): formatter = mticker.PercentFormatter(xmax, decimals, symbol) nose.tools.assert_equal(formatter.format_pct(x, display_range), expected) + # test str.format() style formatter with `pos` + tmp_form = mticker.StrMethodFormatter('{x:03d}-{pos:02d}') + nose.tools.assert_equal('002-01', tmp_form(2, 1)) + def test_percentformatter(): test_cases = ( From 89a1ba1ef4695c7fb5da167fc9c6fb6488f35b71 Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Tue, 29 Mar 2016 17:08:13 -0400 Subject: [PATCH 4/7] DOC: Updated docs for formatters in ticker --- lib/matplotlib/ticker.py | 232 +++++++++++++++++++++++++++------------ 1 file changed, 159 insertions(+), 73 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 526c38ca269e..7b7e664b9138 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -108,35 +108,38 @@ axis. :class:`NullFormatter` - no labels on the ticks + No labels on the ticks :class:`IndexFormatter` - set the strings from a list of labels + Set the strings from a list of labels :class:`FixedFormatter` - set the strings manually for the labels + Set the strings manually for the labels :class:`FuncFormatter` - user defined function sets the labels + User defined function sets the labels :class:`StrMethodFormatter` Use string `format` method :class:`FormatStrFormatter` - use a sprintf format string + Use an old-style sprintf format string :class:`ScalarFormatter` - default formatter for scalars; autopick the fmt string + Default formatter for scalars: autopick the format string :class:`LogFormatter` - formatter for log axes + Formatter for log axes + +:class:`EngFormatter` + Format labels in engineering notation :class:`PercentFormatter` Format labels as a percentage You can derive your own formatter from the Formatter base class by -simply overriding the ``__call__`` method. The formatter class has access -to the axis view and data limits. +simply overriding the ``__call__`` method. The formatter class has +access to the axis view and data limits. To control the major and minor tick label formats, use one of the following methods:: @@ -254,22 +257,32 @@ def set_bounds(self, vmin, vmax): class Formatter(TickHelper): """ - Convert the tick location to a string + Create a string based on a tick value and location. """ # some classes want to see all the locs to help format # individual ones locs = [] def __call__(self, x, pos=None): - """Return the format for tick val x at position pos; pos=None - indicated unspecified""" + """ + Return the format for tick value `x` at position pos. + ``pos=None`` indicates an unspecified location. + """ raise NotImplementedError('Derived must override') def format_data(self, value): + """ + Returns the full string representation of the value with the + position unspecified. + """ return self.__call__(value) def format_data_short(self, value): - """return a short string version""" + """ + Return a short string version of the tick value. + + Defaults to the position-indepedent long value. + """ return self.format_data(value) def get_offset(self): @@ -297,44 +310,58 @@ def fix_minus(self, s): class IndexFormatter(Formatter): """ - format the position x to the nearest i-th label where i=int(x+0.5) + Format the position x to the nearest i-th label where i=int(x+0.5) """ def __init__(self, labels): self.labels = labels self.n = len(labels) def __call__(self, x, pos=None): - """Return the format for tick val x at position pos; pos=None - indicated unspecified""" + """ + Return the format for tick value `x` at position pos. + + The position is ignored and the value is rounded to the nearest + integer, which is used to look up the label. + """ i = int(x + 0.5) - if i < 0: - return '' - elif i >= self.n: + if i < 0 or i >= self.n: return '' else: return self.labels[i] class NullFormatter(Formatter): - 'Always return the empty string' - + """ + Always return the empty string. + """ def __call__(self, x, pos=None): - 'Return the format for tick val *x* at position *pos*' + """ + Returns an empty string for all inputs. + """ return '' class FixedFormatter(Formatter): - 'Return fixed strings for tick labels' + """ + Return fixed strings for tick labels based only on position, not + value. + """ def __init__(self, seq): """ - *seq* is a sequence of strings. For positions ``i < len(seq)`` return - *seq[i]* regardless of *x*. Otherwise return '' + Set the sequence of strings that will be used for labels. """ self.seq = seq self.offset_string = '' def __call__(self, x, pos=None): - 'Return the format for tick val *x* at position *pos*' + """ + Returns the label that matches the position regardless of the + value. + + For positions ``pos < len(seq)``, return `seq[i]` regardless of + `x`. Otherwise return empty string. `seq` is the sequence of + strings that this object was initialized with. + """ if pos is None or pos >= len(self.seq): return '' else: @@ -349,28 +376,40 @@ def set_offset_string(self, ofs): class FuncFormatter(Formatter): """ - User defined function for formatting + Use a user-defined function for formatting. - The function should take in two inputs (tick value *x* and position *pos*) - and return a string + The function should take in two inputs (a tick value ``x`` and a + position ``pos``), and return a string containing the corresponding + tick label. """ def __init__(self, func): self.func = func def __call__(self, x, pos=None): - 'Return the format for tick val *x* at position *pos*' + """ + Return the value of the user defined function. + + `x` and `pos` are passed through as-is. + """ return self.func(x, pos) class FormatStrFormatter(Formatter): """ - Use an old-style ('%' operator) format string to format the tick + Use an old-style ('%' operator) format string to format the tick. + + The format string should have a single variable format (%) in it. + It will be applied to the value (not the postition) of the tick. """ def __init__(self, fmt): self.fmt = fmt def __call__(self, x, pos=None): - 'Return the format for tick val *x* at position *pos*' + """ + Return the formatted label string. + + Only the value `x` is formatted. The position is ignored. + """ return self.fmt % x @@ -401,13 +440,21 @@ class OldScalarFormatter(Formatter): """ def __call__(self, x, pos=None): - 'Return the format for tick val *x* at position *pos*' + """ + Return the format for tick val `x` based on the width of the + axis. + + The position `pos` is ignored. + """ xmin, xmax = self.axis.get_view_interval() d = abs(xmax - xmin) return self.pprint_val(x, d) def pprint_val(self, x, d): + """ + Formats the value `x` based on the size of the axis range `d`. + """ #if the number is not too big and it's an int, format it as an #int if abs(x) < 1e4 and x == int(x): @@ -440,16 +487,20 @@ def pprint_val(self, x, d): class ScalarFormatter(Formatter): """ - Tick location is a plain old number. If useOffset==True and the data range - is much smaller than the data average, then an offset will be determined - such that the tick labels are meaningful. Scientific notation is used for - data < 10^-n or data >= 10^m, where n and m are the power limits set using - set_powerlimits((n,m)). The defaults for these are controlled by the - axes.formatter.limits rc parameter. - + Format tick values as a number. """ - def __init__(self, useOffset=None, useMathText=None, useLocale=None): + """ + Tick value is interpreted as a plain old number. If + ``useOffset==True`` and the data range is much smaller than the + data average, then an offset will be determined such that the + tick labels are meaningful. Scientific notation is used for + ``data < 10^-n`` or ``data >= 10^m``, where ``n`` and ``m`` are + the power limits set using ``set_powerlimits((n,m))``. The + defaults for these are controlled by the + ``axes.formatter.limits`` rc parameter. + """ + # useOffset allows plotting small data ranges with large offsets: for # example: [1+1e-9,1+2e-9,1+3e-9] useMathText will render the offset # and scientific notation in mathtext @@ -494,7 +545,9 @@ def set_useLocale(self, val): useLocale = property(fget=get_useLocale, fset=set_useLocale) def fix_minus(self, s): - """use a unicode minus rather than hyphen""" + """ + Replace hyphens with a unicode minus. + """ if rcParams['text.usetex'] or not rcParams['axes.unicode_minus']: return s else: @@ -509,33 +562,45 @@ def __call__(self, x, pos=None): return self.fix_minus(s) def set_scientific(self, b): - '''True or False to turn scientific notation on or off - see also :meth:`set_powerlimits` - ''' + """ + Turn scientific notation on or off. + + .. seealso:: Method :meth:`set_powerlimits` + """ self._scientific = bool(b) def set_powerlimits(self, lims): - ''' + """ Sets size thresholds for scientific notation. - e.g., ``formatter.set_powerlimits((-3, 4))`` sets the pre-2007 default - in which scientific notation is used for numbers less than 1e-3 or - greater than 1e4. - See also :meth:`set_scientific`. - ''' + Limits is a two-element sequence containing the powers of 10 + that determine the switchover threshold. Numbers below + ``10**lims[0]`` and above ``10**lims[1]`` will be displayed in + scientific notation. + + For example, ``formatter.set_powerlimits((-3, 4))`` sets the + pre-2007 default in which scientific notation is used for + numbers less than 1e-3 or greater than 1e4. + + .. seealso:: Method :meth:`set_scientific` + """ if len(lims) != 2: raise ValueError("'lims' must be a sequence of length 2") self._powerlimits = lims def format_data_short(self, value): - """return a short formatted string representation of a number""" + """ + Return a short formatted string representation of a number. + """ if self._useLocale: return locale.format_string('%-12g', (value,)) else: return '%-12g' % value def format_data(self, value): - 'return a formatted string representation of a number' + """ + Return a formatted string representation of a number. + """ if self._useLocale: s = locale.format_string('%1.10e', (value,)) else: @@ -544,7 +609,9 @@ def format_data(self, value): return self.fix_minus(s) def get_offset(self): - """Return scientific notation, plus offset""" + """ + Return scientific notation, plus offset. + """ if len(self.locs) == 0: return '' s = '' @@ -574,7 +641,9 @@ def get_offset(self): return self.fix_minus(s) def set_locs(self, locs): - 'set the locations of the ticks' + """ + Set the locations of the ticks. + """ self.locs = locs if len(self.locs) > 0: vmin, vmax = self.axis.get_view_interval() @@ -710,14 +779,12 @@ def _formatSciNotation(self, s): class LogFormatter(Formatter): """ - Format values for log axis; - + Format values for log axis. """ def __init__(self, base=10.0, labelOnlyBase=True): """ - *base* is used to locate the decade tick, - which will be the only one to be labeled if *labelOnlyBase* - is ``False`` + `base` is used to locate the decade tick, which will be the only + one to be labeled if `labelOnlyBase` is ``True``. """ self._base = base + 0.0 self.labelOnlyBase = labelOnlyBase @@ -728,7 +795,11 @@ def base(self, base): self._base = base def label_minor(self, labelOnlyBase): - 'switch on/off minor ticks labeling' + """ + Switch minor tick labeling on or off. + + ``labelOnlyBase=True`` to turn off minor ticks. + """ self.labelOnlyBase = labelOnlyBase def __call__(self, x, pos=None): @@ -801,12 +872,14 @@ def pprint_val(self, x, d): class LogFormatterExponent(LogFormatter): """ - Format values for log axis; using ``exponent = log_base(value)`` + Format values for log axis using ``exponent = log_base(value)`` """ - def __call__(self, x, pos=None): - """Return the format for tick val *x* at position *pos*""" + """ + Return the format for tick value `x`. + The position `pos` is ignored. + """ vmin, vmax = self.axis.get_view_interval() vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) d = abs(vmax - vmin) @@ -834,11 +907,15 @@ def __call__(self, x, pos=None): class LogFormatterMathtext(LogFormatter): """ - Format values for log axis; using ``exponent = log_base(value)`` + Format values for log axis using ``exponent = log_base(value)``. """ def __call__(self, x, pos=None): - 'Return the format for tick val *x* at position *pos*' + """ + Return the format for tick value `x`. + + The position `pos` is ignored. + """ b = self._base usetex = rcParams['text.usetex'] @@ -882,7 +959,9 @@ def __call__(self, x, pos=None): class LogitFormatter(Formatter): - '''Probability formatter (using Math text)''' + """ + Probability formatter (using Math text). + """ def __call__(self, x, pos=None): s = '' if 0.01 <= x <= 0.99: @@ -906,14 +985,10 @@ def format_data_short(self, value): class EngFormatter(Formatter): """ - Formats axis values using engineering prefixes to represent powers of 1000, - plus a specified unit, e.g., 10 MHz instead of 1e7. + Formats axis values using engineering prefixes to represent powers + of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7. """ - # the unicode for -6 is the greek letter mu - # commeted here due to bug in pep8 - # (https://github.com/jcrocholl/pep8/issues/271) - # The SI engineering prefixes ENG_PREFIXES = { -24: "y", @@ -936,6 +1011,17 @@ class EngFormatter(Formatter): } def __init__(self, unit="", places=None): + """ + Initializes an engineering notation formatter. + + `unit` is a string containing the abbreviated name of the unit, + suitable for use with single-letter representations of powers of + 1000. For example, 'Hz' or 'm'. + + `places` is the percision with which to display the number, + specified in digits after the decimal point (there will be + between one and three digits before the decimal point). + """ self.unit = unit self.places = places From 864a6cc85b070692f0276fd5b1cf6470cdfe908d Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Tue, 29 Mar 2016 17:10:27 -0400 Subject: [PATCH 5/7] MAINT: Fixed up __all__ in ticker Moved to the top and added missing formatter classes. Probably missing some locators too, but that's for the future. --- lib/matplotlib/ticker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 7b7e664b9138..bc2103be30d3 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -564,7 +564,7 @@ def __call__(self, x, pos=None): def set_scientific(self, b): """ Turn scientific notation on or off. - + .. seealso:: Method :meth:`set_powerlimits` """ self._scientific = bool(b) From c94d0b172b2cac3457d6126c5c22ffd3e27420af Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Thu, 31 Mar 2016 01:35:12 -0400 Subject: [PATCH 6/7] DOC: Wrapped to 72 chars and fixed a couple of missed spots --- lib/matplotlib/ticker.py | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index bc2103be30d3..1e0e56ad63b9 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -2,11 +2,11 @@ Tick locating and formatting ============================ -This module contains classes to support completely configurable tick locating -and formatting. Although the locators know nothing about major or minor -ticks, they are used by the Axis class to support major and minor tick -locating and formatting. Generic tick locators and formatters are provided, -as well as domain specific custom ones.. +This module contains classes to support completely configurable tick +locating and formatting. Although the locators know nothing about major +or minor ticks, they are used by the Axis class to support major and +minor tick locating and formatting. Generic tick locators and +formatters are provided, as well as domain specific custom ones.. Default Formatter @@ -35,8 +35,8 @@ The Locator class is the base class for all tick locators. The locators handle autoscaling of the view limits based on the data limits, and the choosing of tick locations. A useful semi-automatic tick locator is -MultipleLocator. You initialize this with a base, e.g., 10, and it picks axis -limits and ticks that are multiples of your base. +MultipleLocator. You initialize this with a base, e.g., 10, and it +picks axis limits and ticks that are multiples of your base. The Locator subclasses defined here are @@ -56,8 +56,9 @@ logarithmically ticks from min to max :class:`SymmetricalLogLocator` - locator for use with with the symlog norm, works like the `LogLocator` for - the part outside of the threshold and add 0 if inside the limits + locator for use with with the symlog norm, works like the + `LogLocator` for the part outside of the threshold and add 0 if + inside the limits :class:`MultipleLocator` ticks and range are a multiple of base; @@ -790,8 +791,13 @@ def __init__(self, base=10.0, labelOnlyBase=True): self.labelOnlyBase = labelOnlyBase def base(self, base): - """change the *base* for labeling - warning: should always match the - base used for :class:`LogLocator`""" + """ + change the `base` for labeling. + + .. warning:: + Should always match the base used for :class:`LogLocator` + + """ self._base = base def label_minor(self, labelOnlyBase): @@ -803,7 +809,9 @@ def label_minor(self, labelOnlyBase): self.labelOnlyBase = labelOnlyBase def __call__(self, x, pos=None): - """Return the format for tick val *x* at position *pos*""" + """ + Return the format for tick val `x` at position `pos`. + """ vmin, vmax = self.axis.get_view_interval() d = abs(vmax - vmin) b = self._base @@ -834,7 +842,9 @@ def format_data(self, value): return value def format_data_short(self, value): - 'return a short formatted string representation of a number' + """ + Return a short formatted string representation of a number. + """ return '%-12g' % value def pprint_val(self, x, d): @@ -872,7 +882,7 @@ def pprint_val(self, x, d): class LogFormatterExponent(LogFormatter): """ - Format values for log axis using ``exponent = log_base(value)`` + Format values for log axis using ``exponent = log_base(value)``. """ def __call__(self, x, pos=None): """ From 29b2ea6f1f5685a01722239d4fc670dc66758022 Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Thu, 31 Mar 2016 02:13:26 -0400 Subject: [PATCH 7/7] Responses to PR#6253 --- lib/matplotlib/ticker.py | 49 ++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 1e0e56ad63b9..f6dade08e0a2 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -282,7 +282,7 @@ def format_data_short(self, value): """ Return a short string version of the tick value. - Defaults to the position-indepedent long value. + Defaults to the position-independent long value. """ return self.format_data(value) @@ -400,7 +400,7 @@ class FormatStrFormatter(Formatter): Use an old-style ('%' operator) format string to format the tick. The format string should have a single variable format (%) in it. - It will be applied to the value (not the postition) of the tick. + It will be applied to the value (not the position) of the tick. """ def __init__(self, fmt): self.fmt = fmt @@ -489,19 +489,16 @@ def pprint_val(self, x, d): class ScalarFormatter(Formatter): """ Format tick values as a number. + + Tick value is interpreted as a plain old number. If + ``useOffset==True`` and the data range is much smaller than the data + average, then an offset will be determined such that the tick labels + are meaningful. Scientific notation is used for ``data < 10^-n`` or + ``data >= 10^m``, where ``n`` and ``m`` are the power limits set + using ``set_powerlimits((n,m))``. The defaults for these are + controlled by the ``axes.formatter.limits`` rc parameter. """ def __init__(self, useOffset=None, useMathText=None, useLocale=None): - """ - Tick value is interpreted as a plain old number. If - ``useOffset==True`` and the data range is much smaller than the - data average, then an offset will be determined such that the - tick labels are meaningful. Scientific notation is used for - ``data < 10^-n`` or ``data >= 10^m``, where ``n`` and ``m`` are - the power limits set using ``set_powerlimits((n,m))``. The - defaults for these are controlled by the - ``axes.formatter.limits`` rc parameter. - """ - # useOffset allows plotting small data ranges with large offsets: for # example: [1+1e-9,1+2e-9,1+3e-9] useMathText will render the offset # and scientific notation in mathtext @@ -555,7 +552,9 @@ def fix_minus(self, s): return s.replace('-', '\u2212') def __call__(self, x, pos=None): - 'Return the format for tick val *x* at position *pos*' + """ + Return the format for tick value `x` at position `pos`. + """ if len(self.locs) == 0: return '' else: @@ -574,7 +573,7 @@ def set_powerlimits(self, lims): """ Sets size thresholds for scientific notation. - Limits is a two-element sequence containing the powers of 10 + ``lims`` is a two-element sequence containing the powers of 10 that determine the switchover threshold. Numbers below ``10**lims[0]`` and above ``10**lims[1]`` will be displayed in scientific notation. @@ -997,8 +996,15 @@ class EngFormatter(Formatter): """ Formats axis values using engineering prefixes to represent powers of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7. - """ + `unit` is a string containing the abbreviated name of the unit, + suitable for use with single-letter representations of powers of + 1000. For example, 'Hz' or 'm'. + + `places` is the percision with which to display the number, + specified in digits after the decimal point (there will be between + one and three digits before the decimal point). + """ # The SI engineering prefixes ENG_PREFIXES = { -24: "y", @@ -1021,17 +1027,6 @@ class EngFormatter(Formatter): } def __init__(self, unit="", places=None): - """ - Initializes an engineering notation formatter. - - `unit` is a string containing the abbreviated name of the unit, - suitable for use with single-letter representations of powers of - 1000. For example, 'Hz' or 'm'. - - `places` is the percision with which to display the number, - specified in digits after the decimal point (there will be - between one and three digits before the decimal point). - """ self.unit = unit self.places = places