Skip to content

Commit 1e56305

Browse files
Sort numeric text columns by value in native tables (#1406)
The cluster and similarity views store channel labels as strings, so the table proxy model compared them character by character and placed '10' before '2'. Compare numeric text by numeric value instead, and keep non-numeric entries in their string order. Fixes #1405
1 parent bcf01c1 commit 1e56305

3 files changed

Lines changed: 55 additions & 0 deletions

File tree

docs/changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ behavior they verify rather than listed separately.
5050
Escape, and outside clicks release filter focus so global shortcuts resume.
5151
- Display metadata columns containing multiple values in the Cluster and
5252
Similarity Views instead of leaving their cells blank.
53+
- Sort text columns holding numbers, such as the channel column `ch`, by
54+
numeric value in the Cluster and Similarity Views. Channel 2 no longer
55+
appears after channel 10.
5356

5457
### Changed
5558

phy/gui/tests/test_widgets.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,36 @@ def test_table_change_and_sort_2(qtbot, table):
566566
_assert(table.get_ids, [9, 8, 7, 6, 4, 3, 2, 1, 0, 5])
567567

568568

569+
def test_table_sort_numeric_strings(qtbot):
570+
# The `ch` column of the cluster view holds channel labels, which are strings.
571+
# A plain string comparison sorts '10' before '2', so numeric strings have to be
572+
# compared as numbers.
573+
data = [{'id': i, 'ch': ch} for i, ch in enumerate(['2', '10', '1', '21', '3'])]
574+
table = Table(columns=['id', 'ch'], value_names=['id', 'ch'], data=data)
575+
_wait_until_table_ready(qtbot, table)
576+
577+
table.sort_by('ch', 'asc')
578+
_assert(table.get_ids, [2, 0, 4, 1, 3])
579+
580+
table.sort_by('ch', 'desc')
581+
_assert(table.get_ids, [3, 1, 4, 0, 2])
582+
583+
table.close()
584+
585+
586+
def test_table_sort_mixed_strings(qtbot):
587+
# In a column that mixes numbers and free text, the numbers come first in numeric
588+
# order and the remaining entries keep their string ordering.
589+
data = [{'id': i, 'label': label} for i, label in enumerate(['mua', '10', 'good', '2'])]
590+
table = Table(columns=['id', 'label'], value_names=['id', 'label'], data=data)
591+
_wait_until_table_ready(qtbot, table)
592+
593+
table.sort_by('label', 'asc')
594+
_assert(table.get_ids, [3, 1, 2, 0])
595+
596+
table.close()
597+
598+
569599
def test_table_change_metadata_preserves_sort(qtbot):
570600
data = [
571601
{'id': 0, 'count': 30, 'group': 'noise'},

phy/gui/widgets.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import inspect
1010
import json
1111
import logging
12+
import math
1213
import re
1314
import sys
1415
from contextlib import contextmanager
@@ -330,6 +331,25 @@ def predicate(row):
330331
return predicate, True
331332

332333

334+
def _text_sort_key(value):
335+
"""Return a sort key that orders numeric text by value rather than character by character.
336+
337+
Columns such as `ch` hold channel labels, which are strings, so a plain string
338+
comparison places '10' before '2'. Numeric entries sort first, in numeric order, and
339+
the remaining entries keep their string ordering.
340+
341+
"""
342+
text = value if isinstance(value, str) else str(value)
343+
try:
344+
number = float(text)
345+
except ValueError:
346+
return (1, 0.0, text)
347+
# NaN has no consistent ordering, so keep it with the non-numeric entries.
348+
if math.isnan(number):
349+
return (1, 0.0, text)
350+
return (0, number, '')
351+
352+
333353
class _TableModel(QAbstractTableModel):
334354
"""Model backing the native Qt table."""
335355

@@ -438,6 +458,8 @@ def lessThan(self, left, right):
438458
return False
439459
if right_value is None:
440460
return True
461+
if isinstance(left_value, str) or isinstance(right_value, str):
462+
return _text_sort_key(left_value) < _text_sort_key(right_value)
441463
try:
442464
return bool(left_value < right_value)
443465
except TypeError:

0 commit comments

Comments
 (0)