3 """Define functions for handling numbers in SI notation.
\r
7 To output a scale, choose any value on the axis and find the
\r
8 multiplier and prefix for it. Use those to format the rest of the
\r
9 scale. As values can span several orders of magnitude, you have
\r
10 to decide what units to use.
\r
12 >>> xs = (985e-12, 1e-9, 112358e-12)
\r
14 Get the power from the first (or last, or middle, ...) value
\r
16 >>> p = get_power(xs[0])
\r
18 ... print ppSI(x, decimals=2, power=p)
\r
22 >>> print prefix_from_value(xs[0]) + 'N'
\r
27 from numpy import isnan
\r
50 """A dictionary of SI prefixes from 10**24 to 10**-24.
\r
62 _DATA_LABEL_REGEXP = re.compile('^([^(]*[^ ]) ?\(([^)]*)\)$')
\r
63 """Used by :func:`data_label_unit`.
\r
67 def ppSI(value, unit='', decimals=None, power=None, pad=False):
\r
68 """Pretty-print `value` in SI notation.
\r
70 The current implementation ignores `pad` if `decimals` is `None`.
\r
74 >>> x = math.pi * 1e-8
\r
75 >>> print ppSI(x, 'N')
\r
77 >>> print ppSI(x, 'N', 3)
\r
79 >>> print ppSI(x, 'N', 4, power=-12)
\r
81 >>> print ppSI(x, 'N', 5, pad=True)
\r
84 If you want the decimal indented by six spaces with `decimal=2`,
\r
85 `pad` should be the sum of
\r
87 * 6 (places before the decimal point)
\r
88 * 1 (length of the decimal point)
\r
89 * 2 (places after the decimal point)
\r
91 >>> print ppSI(-x, 'N', 2, pad=(6+1+2))
\r
99 if power == None: # auto-detect power
\r
100 power = get_power(value)
\r
102 if decimals == None:
\r
103 format = lambda n: '%f' % n
\r
105 if pad == False: # no padding
\r
106 format = lambda n: '%.*f' % (decimals, n)
\r
108 if pad == True: # auto-generate pad
\r
109 # 1 for ' ', 1 for '-', 3 for number, 1 for '.', and decimals.
\r
111 format = lambda n: '%*.*f' % (pad, decimals, n)
\r
112 return '%s %s%s' % (format(value / pow(10,power)), PREFIX[power], unit)
\r
115 def get_power(value):
\r
116 """Return the SI power for which `0 <= |value|/10**pow < 1000`.
\r
124 >>> get_power(-123)
\r
128 >>> get_power(1e-16)
\r
131 if value != 0 and not isnan(value):
\r
132 # get log10(|value|)
\r
133 value_temp = math.floor(math.log10(math.fabs(value)))
\r
134 # reduce the log10 to a multiple of 3
\r
135 return int(value_temp - (value_temp % 3))
\r
139 def prefix_from_value(value):
\r
140 """Determine the SI power of `value` and return its prefix.
\r
144 >>> prefix_from_value(0)
\r
146 >>> prefix_from_value(1e10)
\r
149 return PREFIX[get_power(value)]
\r
151 def split_data_label(label):
\r
152 """Split `curve.data[i].info['name']` labels into `(name, unit)`.
\r
156 >>> split_data_label('z piezo (m)')
\r
158 >>> split_data_label('deflection (N)')
\r
159 ('deflection', 'N')
\r
161 m = _DATA_LABEL_REGEXP.match(label)
\r
162 assert m != None, label
\r