1 # Copyright (C) 2010-2012 W. Trevor King <wking@drexel.edu>
3 # This file is part of Hooke.
5 # Hooke is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU Lesser General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option) any
10 # Hooke is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with Hooke. If not, see <http://www.gnu.org/licenses/>.
18 """Define functions for handling numbers in SI notation.
22 To output a scale, choose any value on the axis and find the
23 multiplier and prefix for it. Use those to format the rest of the
24 scale. As values can span several orders of magnitude, you have
25 to decide what units to use.
27 >>> xs = (985e-12, 1e-9, 112358e-12)
29 Get the power from the first (or last, or middle, ...) value
31 >>> p = get_power(xs[0])
33 ... print ppSI(x, decimals=2, power=p)
37 >>> print prefix_from_value(xs[0]) + 'N'
42 from numpy import isnan
65 """A dictionary of SI prefixes from 10**24 to 10**-24.
77 _DATA_LABEL_REGEXP = re.compile('^([^(]*[^ ]) ?\(([^)]*)\)$')
78 """Used by :func:`data_label_unit`.
82 def ppSI(value, unit='', decimals=None, power=None, pad=False):
83 """Pretty-print `value` in SI notation.
85 The current implementation ignores `pad` if `decimals` is `None`.
89 >>> x = math.pi * 1e-8
90 >>> print ppSI(x, 'N')
92 >>> print ppSI(x, 'N', 3)
94 >>> print ppSI(x, 'N', 4, power=-12)
96 >>> print ppSI(x, 'N', 5, pad=True)
99 If you want the decimal indented by six spaces with `decimal=2`,
100 `pad` should be the sum of
102 * 6 (places before the decimal point)
103 * 1 (length of the decimal point)
104 * 2 (places after the decimal point)
106 >>> print ppSI(-x, 'N', 2, pad=(6+1+2))
111 if value == None or isnan(value):
114 if power == None: # auto-detect power
115 power = get_power(value)
118 format = lambda n: '%f' % n
120 if pad == False: # no padding
121 format = lambda n: '%.*f' % (decimals, n)
123 if pad == True: # auto-generate pad
124 # 1 for ' ', 1 for '-', 3 for number, 1 for '.', and decimals.
126 format = lambda n: '%*.*f' % (pad, decimals, n)
128 prefix = ' '+PREFIX[power]
130 prefix = 'e%d ' % power
131 return '%s%s%s' % (format(value / pow(10,power)), prefix, unit)
134 def get_power(value):
135 """Return the SI power for which `0 <= |value|/10**pow < 1000`.
150 if value != 0 and not isnan(value):
152 value_temp = math.floor(math.log10(math.fabs(value)))
153 # reduce the log10 to a multiple of 3
154 return int(value_temp - (value_temp % 3))
158 def prefix_from_value(value):
159 """Determine the SI power of `value` and return its prefix.
163 >>> prefix_from_value(0)
165 >>> prefix_from_value(1e10)
168 return PREFIX[get_power(value)]
170 def join_data_label(name, unit):
171 """Create laels for `curve.data[i].info['columns']`.
179 >>> join_data_label('z piezo', 'm')
181 >>> join_data_label('deflection', 'N')
184 return '%s (%s)' % (name, unit)
186 def split_data_label(label):
187 """Split `curve.data[i].info['columns']` labels into `(name, unit)`.
195 >>> split_data_label('z piezo (m)')
197 >>> split_data_label('deflection (N)')
200 m = _DATA_LABEL_REGEXP.match(label)
201 assert m != None, label