Hi Jason, Hi list,

First of all let me say I like the EngFormatter of Jason.
Are there plans to incorparate it into matplotlib? 
I cannot find any indication for this in current svn, but I would like to see 
the EngFormatter in matplotlib. Therefore I tried to include Jasons proposal 
into the ticker.py as a new class EngFormatter including the 
method 'self.format_eng'. 
I made some changes, which might break some of Jasons ideas, but I cannot see
(1) why we need the LogFormatter as base class instead of Formatter?
(2) why one should decide for only decade-labels. Therefore I removed the 
corresponding if-statement. It causes errors () if you use an axes 
including '0', which could be useful for eng-formatting, too.
(3) Could we always use  format_str = "%g %s"   instead of 
specifying 'places'? Because of successive zomming I don't want to 
specify 'places' before calling 'plt.show'. Therefore I introduced "places 
=None" to use "%g %s". 

I attached the new ticker.py and a diff against current svn (I'm sorry I 
couldn't resist to add some white spaces).

Any comments are welcome.

Kind regards,
Matthias Michler

On Wednesday 25 November 2009 01:39:43 Jason Heeris wrote:
> Hi,
>
> 2009/11/18 Jason Heeris <jason.hee...@gmail.com>:
> > In gnuplot, I can do the following:
> >
> > set format x "%.0s %cHz"
> >
> > ...and this will set the x-axis labels (on a semilogx style plot) to
> > be "10 Hz", "100 Hz", "1 kHz", "10 kHz", etc.
>
> I ended up implementing this myself, it wasn't too hard. I've attached
> the code if anyone else is interested. I don't know matplotlib that
> well, so I don't know if there's much duplication of code in there.
>
> I thought I'd CC the dev list in case others think it might be useful.
> If not, sorry for the noise.
>
> Cheers,
> Jason


Index: ticker.py
===================================================================
--- ticker.py	(revision 8000)
+++ ticker.py	(working copy)
@@ -118,6 +118,7 @@
 
 
 from __future__ import division
+import decimal
 import math
 import numpy as np
 from matplotlib import rcParams
@@ -507,23 +508,23 @@
         is ``False``
         """
         self._base = base+0.0
-        self.labelOnlyBase=labelOnlyBase
+        self.labelOnlyBase = labelOnlyBase
         self.decadeOnly = True
 
-    def base(self,base):
+    def base(self, base):
         'change the *base* for labeling - warning: should always match the base used for :class:`LogLocator`'
-        self._base=base
+        self._base = base
 
-    def label_minor(self,labelOnlyBase):
+    def label_minor(self, labelOnlyBase):
         'switch on/off minor ticks labeling'
-        self.labelOnlyBase=labelOnlyBase
+        self.labelOnlyBase = labelOnlyBase
 
 
     def __call__(self, x, pos=None):
         'Return the format for tick val *x* at position *pos*'
         vmin, vmax = self.axis.get_view_interval()
         d = abs(vmax - vmin)
-        b=self._base
+        b = self._base
         if x == 0.0:
             return '0'
         sign = np.sign(x)
@@ -533,13 +534,13 @@
         if not isDecade and self.labelOnlyBase: s = ''
         elif x>10000: s= '%1.0e'%x
         elif x<1: s =  '%1.0e'%x
-        else        : s =  self.pprint_val(x,d)
+        else        : s =  self.pprint_val(x, d)
         if sign == -1:
             s =  '-%s' % s
 
         return self.fix_minus(s)
 
-    def format_data(self,value):
+    def format_data(self, value):
         self.labelOnlyBase = False
         value = cbook.strip_math(self.__call__(value))
         self.labelOnlyBase = True
@@ -554,14 +555,14 @@
         return abs(x-n)<1e-10
 
     def nearest_long(self, x):
-        if x==0: return 0L
-        elif x>0: return long(x+0.5)
+        if x == 0: return 0L
+        elif x > 0: return long(x+0.5)
         else: return long(x-0.5)
 
     def pprint_val(self, x, 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): return '%d' % x
+        if abs(x) < 1e4 and x == int(x): return '%d' % x
 
         if d < 1e-2: fmt = '%1.3e'
         elif d < 1e-1: fmt = '%1.3f'
@@ -572,7 +573,7 @@
         s =  fmt % x
         #print d, x, fmt, s
         tup = s.split('e')
-        if len(tup)==2:
+        if len(tup) == 2:
             mantissa = tup[0].rstrip('0').rstrip('.')
             sign = tup[1][0].replace('+', '')
             exponent = tup[1][1:].lstrip('0')
@@ -645,11 +646,106 @@
             if usetex:
                 s = r'$%s%d^{%d}$'% (sign_string, b, self.nearest_long(fx))
             else:
-                s = r'$\mathdefault{%s%d^{%d}}$'% (sign_string, b, self.nearest_long(fx))
+                s = r'$\mathdefault{%s%d^{%d}}$'% (sign_string, b,
+                                                   self.nearest_long(fx))
 
         return s
 
+class EngFormatter(Formatter):
+    """
+    Formats axis values using engineering prefixes to represent powers of 1000,
+    plus a specified unit, eg. 10 MHz instead of 1e7.
 
+    example:
+    from matplotlib.ticker import EngFormatter
+    formatter = EngFormatter(unit='Hz')
+    ax = plt.subplot(111)
+    ax.xaxis.set_major_formatter(formatter)
+    ax.plot(np.arange(100), np.random.uniform(size=100))
+
+    """
+    # The SI engineering prefixes
+    ENG_PREFIXES = {
+        -24: "y",
+        -21: "z",
+        -18: "a",
+        -15: "f",
+        -12: "p",
+         -9: "n",
+         -6: u"\u03bc", # Greek letter mu
+         -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):
+        self.unit = unit
+        self.places = places
+
+    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)
+
+        if self.places is None:
+            format_str = u"%g %s"
+        elif self.places == 0:
+            format_str = u"%i %s"
+        elif self.places > 0:
+            format_str = (u"%%.%if %%s" % places)
+
+        formatted = format_str % (mant, prefix)
+
+        return formatted.strip()
+
 class Locator(TickHelper):
     """
     Determine the tick locations;

Attachment: ticker.py
Description: application/python

------------------------------------------------------------------------------
Join us December 9, 2009 for the Red Hat Virtual Experience,
a free event focused on virtualization and cloud computing. 
Attend in-depth sessions from your desk. Your couch. Anywhere.
http://p.sf.net/sfu/redhat-sfdev2dev
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users

Reply via email to