Merge pull request #1540 from ixjlyons/siprefix-without-suffix

Support siPrefix with no suffix in SpinBox
This commit is contained in:
Ogi Moore 2021-02-03 22:16:26 -08:00 committed by GitHub
commit 6123e16f54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 73 additions and 20 deletions

View File

@ -65,6 +65,8 @@ SI Unit Conversion Functions
.. autofunction:: pyqtgraph.siEval .. autofunction:: pyqtgraph.siEval
.. autofunction:: pyqtgraph.siParse
Image Preparation Functions Image Preparation Functions
--------------------------- ---------------------------
@ -100,5 +102,3 @@ Miscellaneous Functions
.. autofunction:: pyqtgraph.systemInfo .. autofunction:: pyqtgraph.systemInfo
.. autofunction:: pyqtgraph.exit .. autofunction:: pyqtgraph.exit

View File

@ -31,6 +31,8 @@ spins = [
pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=0.5, minStep=0.01)), pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=0.5, minStep=0.01)),
("Float with SI-prefixed units,<br>dec step=1.0, minStep=0.001", ("Float with SI-prefixed units,<br>dec step=1.0, minStep=0.001",
pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=1.0, minStep=0.001)), pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=1.0, minStep=0.001)),
("Float with SI prefix but no suffix",
pg.SpinBox(value=1e9, siPrefix=True)),
("Float with custom formatting", ("Float with custom formatting",
pg.SpinBox(value=23.07, format='${value:0.02f}', pg.SpinBox(value=23.07, format='${value:0.02f}',
regex='\$?(?P<number>(-?\d+(\.\d+)?)|(-?\.\d+))$')), regex='\$?(?P<number>(-?\d+(\.\d+)?)|(-?\.\d+))$')),

View File

@ -122,7 +122,25 @@ def siParse(s, regex=FLOAT_REGEX, suffix=None):
Example:: Example::
siParse('100 μV") # returns ('100', 'μ', 'V') siParse('100 µV") # returns ('100', 'µ', 'V')
Note that in the above example, the µ symbol is the "micro sign" (UTF-8
0xC2B5), as opposed to the Greek letter mu (UTF-8 0xCEBC).
Parameters
----------
s : str
The string to parse.
regex : re.Pattern, optional
Compiled regular expression object for parsing. The default is a
general-purpose regex for parsing floating point expressions,
potentially containing an SI prefix and a suffix.
suffix : str, optional
Suffix to check for in ``s``. The default (None) indicates there may or
may not be a suffix contained in the string and it is returned if
found. An empty string ``""`` is handled differently: if the string
contains a suffix, it is discarded. This enables interpreting
characters following the numerical value as an SI prefix.
""" """
s = asUnicode(s) s = asUnicode(s)
s = s.strip() s = s.strip()
@ -131,9 +149,14 @@ def siParse(s, regex=FLOAT_REGEX, suffix=None):
raise ValueError("String '%s' does not have the expected suffix '%s'" % (s, suffix)) raise ValueError("String '%s' does not have the expected suffix '%s'" % (s, suffix))
s = s[:-len(suffix)] + 'X' # add a fake suffix so the regex still picks up the si prefix s = s[:-len(suffix)] + 'X' # add a fake suffix so the regex still picks up the si prefix
# special case: discard any extra characters if suffix is explicitly empty
if suffix == "":
s += 'X'
m = regex.match(s) m = regex.match(s)
if m is None: if m is None:
raise ValueError('Cannot parse number "%s"' % s) raise ValueError('Cannot parse number "%s"' % s)
try: try:
sip = m.group('siPrefix') sip = m.group('siPrefix')
except IndexError: except IndexError:

View File

@ -424,5 +424,29 @@ def test_eq():
assert not eq(set(range(10)), set(range(9))) assert not eq(set(range(10)), set(range(9)))
if __name__ == '__main__': @pytest.mark.parametrize("s,suffix,expected", [
test_interpolateArray() # usual cases
("100 uV", "V", ("100", "u", "V")),
("100 µV", "V", ("100", "µ", "V")),
("4.2 nV", None, ("4.2", "n", "V")),
("1.2 m", "m", ("1.2", "", "m")),
("1.2 m", None, ("1.2", "", "m")),
("5.0e9", None, ("5.0e9", "", "")),
("2 units", "units", ("2", "", "units")),
# siPrefix with explicit empty suffix
("1.2 m", "", ("1.2", "m", "")),
("5.0e-9 M", "", ("5.0e-9", "M", "")),
# weirder cases that should return the reasonable thing
("4.2 nV", "nV", ("4.2", "", "nV")),
("4.2 nV", "", ("4.2", "n", "")),
("1.2 j", "", ("1.2", "", "")),
("1.2 j", None, ("1.2", "", "j")),
# expected error cases
("100 uV", "v", ValueError),
])
def test_siParse(s, suffix, expected):
if isinstance(expected, tuple):
assert pg.siParse(s, suffix=suffix) == expected
else:
with pytest.raises(expected):
pg.siParse(s, suffix=suffix)

View File

@ -132,8 +132,10 @@ class SpinBox(QtGui.QAbstractSpinBox):
siPrefix (bool) If True, then an SI prefix is automatically prepended siPrefix (bool) If True, then an SI prefix is automatically prepended
to the units and the value is scaled accordingly. For example, to the units and the value is scaled accordingly. For example,
if value=0.003 and suffix='V', then the SpinBox will display if value=0.003 and suffix='V', then the SpinBox will display
"300 mV" (but a call to SpinBox.value will still return 0.003). Default "300 mV" (but a call to SpinBox.value will still return 0.003). In case
is False. the value represents a dimensionless quantity that might span many
orders of magnitude, such as a Reynold's number, an SI
prefix is allowed with no suffix. Default is False.
step (float) The size of a single step. This is used when clicking the up/ step (float) The size of a single step. This is used when clicking the up/
down arrows, when rolling the mouse wheel, or when pressing down arrows, when rolling the mouse wheel, or when pressing
keyboard arrows while the widget has keyboard focus. Note that keyboard arrows while the widget has keyboard focus. Note that
@ -466,7 +468,7 @@ class SpinBox(QtGui.QAbstractSpinBox):
# format the string # format the string
val = self.value() val = self.value()
if self.opts['siPrefix'] is True and len(self.opts['suffix']) > 0: if self.opts['siPrefix'] is True:
# SI prefix was requested, so scale the value accordingly # SI prefix was requested, so scale the value accordingly
if self.val == 0 and prev is not None: if self.val == 0 and prev is not None:
@ -545,7 +547,7 @@ class SpinBox(QtGui.QAbstractSpinBox):
return False return False
# check suffix # check suffix
if suffix != self.opts['suffix'] or (suffix == '' and siprefix != ''): if suffix != self.opts['suffix']:
return False return False
# generate value # generate value

View File

@ -23,6 +23,7 @@ def test_SpinBox_defaults():
(1.45, '1.45 PSI', dict(int=False, decimals=6, suffix='PSI', siPrefix=True)), (1.45, '1.45 PSI', dict(int=False, decimals=6, suffix='PSI', siPrefix=True)),
(1.45e-3, '1.45 mPSI', dict(int=False, decimals=6, suffix='PSI', siPrefix=True)), (1.45e-3, '1.45 mPSI', dict(int=False, decimals=6, suffix='PSI', siPrefix=True)),
(-2500.3427, '$-2500.34', dict(int=False, format='${value:0.02f}')), (-2500.3427, '$-2500.34', dict(int=False, format='${value:0.02f}')),
(1000, '1 k', dict(siPrefix=True, suffix="")),
]) ])
def test_SpinBox_formatting(value, expected_text, opts): def test_SpinBox_formatting(value, expected_text, opts):
sb = pg.SpinBox(**opts) sb = pg.SpinBox(**opts)
@ -35,10 +36,11 @@ def test_SpinBox_formatting(value, expected_text, opts):
@pytest.mark.parametrize("suffix", ["", "V"]) @pytest.mark.parametrize("suffix", ["", "V"])
def test_SpinBox_gui_set_value(suffix): def test_SpinBox_gui_set_value(suffix):
sb = pg.SpinBox(suffix=suffix) sb = pg.SpinBox(suffix=suffix)
sb.lineEdit().setText('0.1' + suffix) sb.lineEdit().setText('0.1' + suffix)
sb.editingFinishedEvent() sb.editingFinishedEvent()
assert sb.value() == 0.1 assert sb.value() == 0.1
if suffix != '':
sb.lineEdit().setText('0.1 m' + suffix) sb.lineEdit().setText('0.1 m' + suffix)
sb.editingFinishedEvent() sb.editingFinishedEvent()
assert sb.value() == 0.1e-3 assert sb.value() == 0.1e-3