prettyformat -> hooke.util.si, and 'plot SI format' option to GUI
authorW. Trevor King <wking@drexel.edu>
Sat, 31 Jul 2010 19:13:49 +0000 (15:13 -0400)
committerW. Trevor King <wking@drexel.edu>
Sat, 31 Jul 2010 19:13:49 +0000 (15:13 -0400)
hooke/ui/gui/__init__.py
hooke/ui/gui/curve.py [deleted file]
hooke/ui/gui/panel/plot.py
hooke/ui/gui/prettyformat.py [deleted file]
hooke/util/si.py [new file with mode: 0644]

index 5dac90f936c84f579b42255b017cbdd16b415eda..38d00bd002d063c4dffdd01a142e4dfa9d7a1f4a 100644 (file)
@@ -34,7 +34,6 @@ from . import menu as menu
 from . import navbar as navbar\r
 from . import panel as panel\r
 from .panel.propertyeditor import prop_from_argument, prop_from_setting\r
-from . import prettyformat as prettyformat\r
 from . import statusbar as statusbar\r
 \r
 \r
@@ -960,15 +959,12 @@ class GUI (UserInterface):
             Setting(section=self.setting_section, option='plot legend',\r
                     value=True,\r
                     help='Enable/disable the plot legend.'),\r
-            Setting(section=self.setting_section, option='plot x format',\r
-                    value='None',\r
-                    help='Display format for plot x values.'),\r
-            Setting(section=self.setting_section, option='plot y format',\r
-                    value='None',\r
-                    help='Display format for plot y values.'),\r
-            Setting(section=self.setting_section, option='plot zero',\r
-                    value=0,\r
-                    help='Select "0" vs. e.g. "0.00" for plot axes?'),\r
+            Setting(section=self.setting_section, option='plot SI format',\r
+                    value='True',\r
+                    help='Enable/disable SI plot axes numbering.'),\r
+            Setting(section=self.setting_section, option='plot decimals',\r
+                    value=2,\r
+                    help='Number of decimal places to show if "plot SI format" is enabled.'),\r
             Setting(section=self.setting_section, option='folders-workdir',\r
                     value='.',\r
                     help='This should probably go...'),\r
diff --git a/hooke/ui/gui/curve.py b/hooke/ui/gui/curve.py
deleted file mode 100644 (file)
index 1a8c688..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/env python\r
-\r
-'''\r
-curve.py\r
-\r
-Curve and related classes for Hooke.\r
-\r
-Copyright 2010 by Dr. Rolf Schmidt (Concordia University, Canada)\r
-\r
-This program is released under the GNU General Public License version 2.\r
-'''\r
-\r
-from  matplotlib.ticker import Formatter\r
-import lib.prettyformat\r
-\r
-class Curve(object):\r
-\r
-    def __init__(self):\r
-        self.color = 'blue'\r
-        self.decimals = Decimals()\r
-        self.destination = Destination()\r
-        self.label = ''\r
-        self.legend = False\r
-        self.linewidth = 1\r
-        self.prefix = Prefix()\r
-        self.size = 0.5\r
-        self.style = 'plot'\r
-        self.title = ''\r
-        self.units = Units()\r
-        self.visible = True\r
-        self.x = []\r
-        self.y = []\r
-\r
-\r
-class Data(object):\r
-\r
-    def __init__(self):\r
-        self.x = []\r
-        self.y = []\r
-\r
-\r
-class Decimals(object):\r
-\r
-    def __init__(self):\r
-        self.x = 2\r
-        self.y = 2\r
-\r
-\r
-class Destination(object):\r
-\r
-    def __init__(self):\r
-        self.column = 1\r
-        self.row = 1\r
-\r
-\r
-class Prefix(object):\r
-\r
-    def __init__(self):\r
-        self.x = 'n'\r
-        self.y = 'p'\r
-\r
-\r
-class PrefixFormatter(Formatter):\r
-    '''\r
-    Formatter (matplotlib) class that uses power prefixes.\r
-    '''\r
-    def __init__(self, decimals=2, prefix='n', use_zero=True):\r
-        self.decimals = decimals\r
-        self.prefix = prefix\r
-        self.use_zero = use_zero\r
-\r
-    def __call__(self, x, pos=None):\r
-        'Return the format for tick val *x* at position *pos*'\r
-        if self.use_zero:\r
-            if x == 0:\r
-                return '0'\r
-        multiplier = lib.prettyformat.get_exponent(self.prefix)\r
-        decimals_str = '%.' + str(self.decimals) + 'f'\r
-        return decimals_str % (x / (10 ** multiplier))\r
-\r
-\r
-class Units(object):\r
-\r
-    def __init__(self):\r
-        self.x = ''\r
-        self.y = ''\r
index 2281812c0880ea3acdbbba875e8baa52cfaf5cbe..c8e0dc2136ed119dbe56e3e77e1bf43d5503987d 100644 (file)
@@ -15,12 +15,29 @@ matplotlib.use('WXAgg')  # use wxpython with antigrain (agg) rendering
 from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas\r
 from matplotlib.backends.backend_wx import NavigationToolbar2Wx as NavToolbar\r
 from matplotlib.figure import Figure\r
+from matplotlib.ticker import Formatter\r
 import wx\r
 \r
 from ....util.callback import callback, in_callback\r
+from ....util.si import ppSI, split_data_label\r
 from . import Panel\r
 \r
 \r
+class HookeFormatter (Formatter):\r
+    """:class:`matplotlib.ticker.Formatter` using SI prefixes.\r
+    """\r
+    def __init__(self, unit='', decimals=2):\r
+        self.decimals = decimals\r
+        self.unit = unit\r
+\r
+    def __call__(self, x, pos=None):\r
+        """Return the format for tick val `x` at position `pos`.\r
+        """\r
+        if x == 0:\r
+            return '0'\r
+        return ppSI(value=x, unit=self.unit, decimals=self.decimals)\r
+\r
+\r
 class PlotPanel (Panel, wx.Panel):\r
     """UI for graphical curve display.\r
     """\r
@@ -115,7 +132,6 @@ class PlotPanel (Panel, wx.Panel):
             #self.SetStatusText(coordinateString)\r
 \r
     def _resize_canvas(self):\r
-        print 'resizing'\r
         w,h = self.GetClientSize()\r
         tw,th = self._c['toolbar'].GetSizeTuple()\r
         dpi = float(self._c['figure'].get_dpi())\r
@@ -134,10 +150,8 @@ class PlotPanel (Panel, wx.Panel):
         self.update(config=config)\r
 \r
     def update(self, config={}):\r
-        print 'updating'\r
-        x_format = config['plot x format']\r
-        y_format = config['plot y format']\r
-        zero = config['plot zero']\r
+        x_name = 'z piezo (m)'\r
+        y_name = 'deflection (m)'\r
 \r
         self._c['figure'].clear()\r
         self._c['figure'].suptitle(\r
@@ -145,17 +159,19 @@ class PlotPanel (Panel, wx.Panel):
             fontsize=12)\r
         axes = self._c['figure'].add_subplot(1, 1, 1)\r
 \r
-#        if x_format != 'None':\r
-#            f = lib.curve.PrefixFormatter(curve.decimals.x, curve.prefix.x, use_zero)\r
-#            axes.xaxis.set_major_formatter(f)\r
-#        if y_format != 'None':\r
-#            f = lib.curve.PrefixFormatter(curve.decimals.y, curve.prefix.y, use_zero)\r
-#            axes.yaxis.set_major_formatter(f)\r
-\r
-        x_name = 'z piezo (m)'\r
-        y_name = 'deflection (m)'\r
-        #axes.set_xlabel(x_name)\r
-        #axes.set_ylabel(y_name)\r
+        if config['plot si format'] == 'True':  # TODO: config should convert\r
+            d = int(config['plot decimals'])  # TODO: config should convert\r
+            x_n, x_unit = split_data_label(x_name)\r
+            y_n, y_unit = split_data_label(y_name)\r
+            fx = HookeFormatter(decimals=d, unit=x_unit)\r
+            axes.xaxis.set_major_formatter(fx)\r
+            fy = HookeFormatter(decimals=d, unit=y_unit)\r
+            axes.yaxis.set_major_formatter(fy)\r
+            axes.set_xlabel(x_n)\r
+            axes.set_ylabel(y_n)\r
+        else:\r
+            axes.set_xlabel(x_name)\r
+            axes.set_ylabel(y_name)\r
 \r
         self._c['figure'].hold(True)\r
         for i,data in enumerate(self._curve.data):\r
@@ -166,3 +182,5 @@ class PlotPanel (Panel, wx.Panel):
         if config['plot legend'] == 'True':  # HACK: config should convert\r
             axes.legend(loc='best')\r
         self._c['canvas'].draw()\r
+\r
+#  LocalWords:  matplotlib\r
diff --git a/hooke/ui/gui/prettyformat.py b/hooke/ui/gui/prettyformat.py
deleted file mode 100644 (file)
index 0c0a662..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-# Copyright\r
-\r
-"""Simple Python function to format values with nice prefixes.\r
-"""\r
-\r
-import math\r
-from numpy import isnan\r
-\r
-\r
-def pretty_format(value, unit='', decimals=-1, multiplier=0, leading_spaces=False):\r
-    if value == 0:\r
-        return '0'\r
-    if isnan(value):\r
-        return 'NaN'\r
-\r
-    output_str = ''\r
-    leading_spaces_int = 0\r
-    if leading_spaces:\r
-        leading_spaces_int = 5\r
-    #automatic setting of multiplier\r
-    if multiplier == 0:\r
-        multiplier=get_multiplier(value)\r
-    unit_str = ''\r
-    if unit:\r
-        unit_str = ' ' + get_prefix(multiplier) + unit\r
-    if decimals >= 0:\r
-        format_str = '% ' + repr(leading_spaces_int + decimals) + '.' + repr(decimals) + 'f'\r
-        output_str = format_str % (value / multiplier) + unit_str\r
-    else:\r
-        output_str = str(value / multiplier) + unit_str\r
-\r
-    if decimals < 0:\r
-        output_str = str(value / multiplier) + ' ' + get_prefix(value / multiplier) + unit\r
-\r
-    if leading_spaces_int == 0:\r
-        output_str = output_str.lstrip()\r
-\r
-    return output_str\r
-\r
-def get_multiplier(value):\r
-    return pow(10, get_power(value))\r
-\r
-def get_power(value):\r
-    if value != 0 and not isnan(value):\r
-        #get the log10 from value (make sure the value is not negative)\r
-        value_temp = math.floor(math.log10(math.fabs(value)))\r
-        #reduce the log10 to a multiple of 3 and return it\r
-        return value_temp - (value_temp % 3)\r
-    else:\r
-        return 0\r
-\r
-def get_exponent(prefix):\r
-    #set up a dictionary to find the exponent\r
-    exponent = {\r
-        'Y': 24,\r
-        'Z': 21,\r
-        'E': 18,\r
-        'P': 15,\r
-        'T': 12,\r
-        'G': 9,\r
-        'M': 6,\r
-        'k': 3,\r
-        '': 0,\r
-        'm': -3,\r
-        u'\u00B5': -6,\r
-        'n': -9,\r
-        'p': -12,\r
-        'f': -15,\r
-        'a': -18,\r
-        'z': -21,\r
-        'y': -24,\r
-    }\r
-    if exponent.has_key(prefix):\r
-        return exponent[prefix]\r
-    else:\r
-        return 1\r
-\r
-def get_prefix(value):\r
-    #set up a dictionary to find the prefix\r
-    prefix = {\r
-        24: lambda: 'Y',\r
-        21: lambda: 'Z',\r
-        18: lambda: 'E',\r
-        15: lambda: 'P',\r
-        12: lambda: 'T',\r
-        9: lambda: 'G',\r
-        6: lambda: 'M',\r
-        3: lambda: 'k',\r
-        0: lambda: '',\r
-        -3: lambda: 'm',\r
-        -6: lambda: u'\u00B5',\r
-        -9: lambda: 'n',\r
-        -12: lambda: 'p',\r
-        -15: lambda: 'f',\r
-        -18: lambda: 'a',\r
-        -21: lambda: 'z',\r
-        -24: lambda: 'y',\r
-    }\r
-    if value != 0 and not isnan(value):\r
-        #get the log10 from value\r
-        value_temp = math.floor(math.log10(math.fabs(value)))\r
-    else:\r
-        value_temp = 0\r
-    #reduce the log10 to a multiple of 3 and create the return string\r
-    return prefix.get(value_temp - (value_temp % 3))()\r
-\r
-'''\r
-test_value=-2.4115665714484597e-008\r
-print 'Value: '+str(test_value)+')'\r
-print 'pretty_format example (value, unit)'\r
-print pretty_format(test_value, 'N')\r
-print'-----------------------'\r
-print 'pretty_format example (value, unit, decimals)'\r
-print pretty_format(test_value, 'N', 3)\r
-print'-----------------------'\r
-print 'pretty_format example (value, unit, decimals, multiplier)'\r
-print pretty_format(test_value, 'N', 5, 0.000001)\r
-print'-----------------------'\r
-print 'pretty_format example (value, unit, decimals, multiplier, leading spaces)'\r
-print pretty_format(0.0166276297705, 'N', 3, 0.001, True)\r
-print pretty_format(0.00750520813323, 'N', 3, 0.001, True)\r
-print pretty_format(0.0136453282825, 'N', 3, 0.001, True)\r
-'''\r
-'''\r
-#to output a scale:\r
-#choose any value on the axis and find the multiplier and prefix for it\r
-#use those to format the rest of the scale\r
-#as values can span several orders of magnitude, you have to decide what units to use\r
-\r
-#tuple of values:\r
-scale_values=0.000000000985, 0.000000001000, 0.000000001015\r
-#use this element (change to 1 or 2 to see the effect on the scale and label)\r
-index=0\r
-#get the multiplier from the value at index\r
-multiplier=get_multiplier(scale_values[index])\r
-print '\nScale example'\r
-decimals=3\r
-#print the scale\r
-for aValue in scale_values: print decimalFormat(aValue/multiplier, decimals),\r
-#print the scale label using the value at index\r
-print '\n'+get_prefix(scale_values[index])+'N'\r
-'''\r
diff --git a/hooke/util/si.py b/hooke/util/si.py
new file mode 100644 (file)
index 0000000..ebb3c19
--- /dev/null
@@ -0,0 +1,163 @@
+# Copyright\r
+\r
+"""Define functions for handling numbers in SI notation.\r
+\r
+Notes\r
+-----\r
+To output a scale, choose any value on the axis and find the\r
+multiplier and prefix for it.  Use those to format the rest of the\r
+scale.  As values can span several orders of magnitude, you have\r
+to decide what units to use.\r
+\r
+>>> xs = (985e-12, 1e-9, 112358e-12)\r
+\r
+Get the power from the first (or last, or middle, ...) value\r
+\r
+>>> p = get_power(xs[0])\r
+>>> for x in xs:\r
+...     print ppSI(x, decimals=2, power=p)\r
+985.00 p\r
+1000.00 p\r
+112358.00 p\r
+>>> print prefix_from_value(xs[0]) + 'N'\r
+pN\r
+"""\r
+\r
+import math\r
+from numpy import isnan\r
+import re\r
+\r
+\r
+PREFIX = {\r
+    24: 'Y',\r
+    21: 'Z',\r
+    18: 'E',\r
+    15: 'P',\r
+    12: 'T',\r
+    9: 'G',\r
+    6: 'M',\r
+    3: 'k',\r
+    0: '',\r
+    -3: 'm',\r
+    -6: u'\u00B5',\r
+    -9: 'n',\r
+    -12: 'p',\r
+    -15: 'f',\r
+    -18: 'a',\r
+    -21: 'z',\r
+    -24: 'y',\r
+    }\r
+"""A dictionary of SI prefixes from 10**24 to 10**-24.\r
+\r
+Examples\r
+--------\r
+>>> PREFIX[0]\r
+''\r
+>>> PREFIX[6]\r
+'M'\r
+>>> PREFIX[-9]\r
+'n'\r
+"""\r
+\r
+_DATA_LABEL_REGEXP = re.compile('^([^(]*[^ ]) ?\(([^)]*)\)$')\r
+"""Used by :func:`data_label_unit`.\r
+"""\r
+\r
+\r
+def ppSI(value, unit='', decimals=None, power=None, pad=False):\r
+    """Pretty-print `value` in SI notation.\r
+\r
+    The current implementation ignores `pad` if `decimals` is `None`.\r
+\r
+    Examples\r
+    --------\r
+    >>> x = math.pi * 1e-8\r
+    >>> print ppSI(x, 'N')\r
+    31.415927 nN\r
+    >>> print ppSI(x, 'N', 3)\r
+    31.416 nN\r
+    >>> print ppSI(x, 'N', 4, power=-12)\r
+    31415.9265 pN\r
+    >>> print ppSI(x, 'N', 5, pad=True)\r
+       31.41593 nN\r
+\r
+    If you want the decimal indented by six spaces with `decimal=2`,\r
+    `pad` should be the sum of\r
+\r
+    * 6 (places before the decimal point)\r
+    * 1 (length of the decimal point)\r
+    * 2 (places after the decimal point)\r
+\r
+    >>> print ppSI(-x, 'N', 2, pad=(6+1+2))\r
+       -31.42 nN\r
+    """\r
+    if value == 0:\r
+        return '0'\r
+    if isnan(value):\r
+        return 'NaN'\r
+\r
+    if power == None:  # auto-detect power\r
+        power = get_power(value)\r
+\r
+    if decimals == None:\r
+        format = lambda n: '%f' % n\r
+    else:\r
+        if pad == False:  # no padding\r
+            format = lambda n: '%.*f' % (decimals, n)            \r
+        else:\r
+            if pad == True:  # auto-generate pad\r
+                # 1 for ' ', 1 for '-', 3 for number, 1 for '.', and decimals.\r
+                pad = 6 + decimals\r
+            format = lambda n: '%*.*f' % (pad, decimals, n)\r
+    return '%s %s%s' % (format(value / pow(10,power)), PREFIX[power], unit)\r
+\r
+\r
+def get_power(value):\r
+    """Return the SI power for which `0 <= |value|/10**pow < 1000`. \r
+    \r
+    Exampes\r
+    -------\r
+    >>> get_power(0)\r
+    0\r
+    >>> get_power(123)\r
+    0\r
+    >>> get_power(-123)\r
+    0\r
+    >>> get_power(1e8)\r
+    6\r
+    >>> get_power(1e-16)\r
+    -18\r
+    """\r
+    if value != 0 and not isnan(value):\r
+        # get log10(|value|)\r
+        value_temp = math.floor(math.log10(math.fabs(value)))\r
+        # reduce the log10 to a multiple of 3\r
+        return int(value_temp - (value_temp % 3))\r
+    else:\r
+        return 0\r
+\r
+def prefix_from_value(value):\r
+    """Determine the SI power of `value` and return its prefix.\r
+\r
+    Examples\r
+    --------\r
+    >>> prefix_from_value(0)\r
+    ''\r
+    >>> prefix_from_value(1e10)\r
+    'G'\r
+    """\r
+    return PREFIX[get_power(value)]\r
+\r
+def split_data_label(label):\r
+    """Split `curve.data[i].info['name']` labels into `(name, unit)`.\r
+\r
+    Examples\r
+    --------\r
+    >>> split_data_label('z piezo (m)')\r
+    ('z piezo', 'm')\r
+    >>> split_data_label('deflection (N)')\r
+    ('deflection', 'N')\r
+    """\r
+    m = _DATA_LABEL_REGEXP.match(label)\r
+    assert m != None, label\r
+    return m.groups()\r