8000 [ENH?] EngFormatter: add the possibility to remove the space before the SI prefix · Issue #6533 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content
[ENH?] EngFormatter: add the possibility to remove the space before the SI prefix  #6533
Closed
@afvincent

Description

@afvincent

Discussing with some of my colleagues who are using other plotting softwares than Matplotlib, I noticed that their formatting of the engineering notation is a bit different from the one implemented in ticker.EngFormatter. Some softwares append directly the prefix/unit to the tick value (e.g. "1.23µs", vs "1.23 µs" with Matplotlib). I am not sure it is really OK from the (English) typography point of view but I guess they are doing this due to the rather limited ticklablel space.

I wonder how interesting it may be to add such a possibility to the EngFormatter in Matplotlib. As some users may prefer "1.23µs" over "1.23 µs", I would say it's worth adding it to EngFormatter.

If it is added to EngFormatter, I guess the major pitfall would be the default values… IMO, the current behavior of Matplotlib is the best one when EngFormatter is instantiated with a unit. However, when it is instantatied without unit (unit=""), I wouldn't be categorical about the fact that "1.23 µ" is better than "1.23µ". So I don't really know if one should use by default a space separator between the value and the prefix/unit, or not…

I wrote a small demonstration of what could be easily done with the EngFormatterclass (keeping the current Matplotlib behavior as the default one). It is > 100 lines because I directly copy-pasted the source code of ticker.EngFormatter. I've put the changes between <ENH> and <\ENH> tags. NB: the code includes a bug fix similar to PR #6014 .

from __future__ import division, print_function, unicode_literals
import decimal
import math
import numpy as np
from matplotlib.ticker import Formatter


# Proposed "enhancement" of the EngFormatter
class EnhancedEngFormatter(Formatter):
    """
    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",
        -21: "z",
        -18: "a",
        -15: "f",
        -12: "p",
         -9: "n",
         -6: "\u03bc",
         -3: "m",
          0: "",
          3: "k",
          6: "M",
          9: "G",
         12: "T",
         15: "P",
         18: "E",
         21: "Z",
         24: "Y"
    }

    def __init__(self, unit="", places=None, space_sep=True):
        """ Parameters
            ----------
            unit: str (default: "")
                Unit symbol to use.

            places: int (default: None)
                Number of digits after the decimal point.
                If it is None, falls back to the floating point format '%g'.

            space_sep: boolean (default: True)
                If True, a (single) space is used between the value and the
                prefix/unit, else the prefix/unit is directly appended to the
                value.
        """
        self.unit = unit
        self.places = places
        # <ENH>
        if space_sep is True:
            self.sep = " "  # 1 space
        else:
            self.sep = ""  # no space
        # <\ENH>

    def __call__(self, x, pos=None):
        s = "%s%s" % (self.format_eng(x), self.unit)
        return self.fix_minus(s)

    def format_eng(self, num):
        """ Formats a number in engineering notation, appending a letter
        representing the power of 1000 of the original number. Some examples:

        >>> format_eng(0)       # for self.places = 0
        '0'

        >>> format_eng(1000000) # for self.places = 1
        '1.0 M'

        >>> format_eng("-1e-6") # for self.places = 2
        u'-1.00 \u03bc'

        @param num: the value to represent
        @type num: either a numeric value or a string that can be converted to
                   a numeric value (as per decimal.Decimal constructor)

        @return: engineering formatted string
        """

        dnum = decimal.Decimal(str(num))

        sign = 1

        if dnum < 0:
            sign = -1
            dnum = -dnum

        if dnum != 0:
            pow10 = decimal.Decimal(int(math.floor(dnum.log10() / 3) * 3))
        else:
            pow10 = decimal.Decimal(0)

        pow10 = pow10.min(max(self.ENG_PREFIXES.keys()))
        pow10 = pow10.max(min(self.ENG_PREFIXES.keys()))

        prefix = self.ENG_PREFIXES[int(pow10)]

        mant = sign * dnum / (10 ** pow10)

        # <ENH>
        if self.places is None:
            format_str = "%g{sep:s}%s".format(sep=self.sep)
        elif self.places == 0:
            format_str = "%i{sep:s}%s".format(sep=self.sep)
        elif self.places > 0:
            format_str = "%.{p:i}f{sep:s}%s".format(p=self.places,
                                                    sep=self.sep)
        # <\ENH>

        formatted = format_str % (mant, prefix)

        formatted = formatted.strip()
        if (self.unit != "") and (prefix == self.ENG_PREFIXES[0]):
            # <ENH>
            formatted = formatted + self.sep
            # <\ENH>

        return formatted


# DEMO
def demo_formatter(**kwargs):
    """ Print the strings produced by the EnhancedEngFormatter for a list of
        arbitrary test values.
    """
    TEST_VALUES = [1.23456789e-6, 0.1, 1, 999.9, 1001]
    unit = kwargs.get('unit', "")
    space_sep = kwargs.get('space_sep', True)
    formatter = EnhancedEngFormatter(**kwargs)

    print("\n[Case: unit='{u:s}' & space_sep={s}]".format(u=unit, s=space_sep))
    print(*["{tst};".format(tst=formatter(value)) for value in TEST_VALUES])

if __name__ == '__main__':
    """ Matplotlib current behavior (w/ space separator) """
    demo_formatter(unit="s", space_sep=True)
    # >> 1.23457 μs; 100 ms; 1 s; 999.9 s; 1.001 ks;
    demo_formatter(unit="", space_sep=True)
    # >> 1.23457 μ; 100 m; 1; 999.9; 1.001 k;

    """ New possibility (w/o space separator) """
    demo_formatter(unit="s", space_sep=False)
    # >> 1.23457μs; 100ms; 1s; 999.9s; 1.001ks;
    demo_formatter(unit="", space_sep=False)
    # >> 1.23457μ; 100m; 1; 999.9; 1.001k;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0