diff --git a/orangecontrib/text/widgets/owsemanticviewer.py b/orangecontrib/text/widgets/owsemanticviewer.py index cb83be568..f558e8685 100644 --- a/orangecontrib/text/widgets/owsemanticviewer.py +++ b/orangecontrib/text/widgets/owsemanticviewer.py @@ -99,6 +99,20 @@ def __init__(self): self.verticalHeader().hide() +class DocumentsModel(PyTableModel): + @staticmethod + def _argsortData(data: np.ndarray, order: Qt.SortOrder) -> np.ndarray: + # put NaNs last when sorting + if data.dtype not in (float, int): + data = np.char.lower(data) + indices = np.argsort(data, kind='mergesort') + if order == Qt.DescendingOrder: + indices = indices[::-1] + if data.dtype == float: + return np.roll(indices, -np.isnan(data).sum()) + return indices + + class DisplayDocument: Document, Section, Sentence = range(3) ITEMS = ["Document", "Section", "Sentence"] @@ -232,7 +246,7 @@ def _setup_gui(self): gui.rubber(self.controlArea) # Main area - model = PyTableModel(parent=self) + model = DocumentsModel(parent=self) self._list_view = SemanticListView() self._list_view.setModel(model) self._list_view.selectionModel().selectionChanged.connect( diff --git a/orangecontrib/text/widgets/tests/test_owsemanticviewer.py b/orangecontrib/text/widgets/tests/test_owsemanticviewer.py index 380fb4de5..3015476ed 100644 --- a/orangecontrib/text/widgets/tests/test_owsemanticviewer.py +++ b/orangecontrib/text/widgets/tests/test_owsemanticviewer.py @@ -3,6 +3,7 @@ from typing import List from unittest.mock import Mock, patch +import numpy as np from AnyQt.QtCore import Qt from AnyQt.QtTest import QSignalSpy from AnyQt.QtWidgets import QTableView @@ -13,7 +14,7 @@ from orangecontrib.text import Corpus from orangecontrib.text.semantic_search import SemanticSearch from orangecontrib.text.widgets.owsemanticviewer import OWSemanticViewer, \ - run, DisplayDocument + run, DisplayDocument, DocumentsModel, SemanticListView def create_words_table(words: List) -> Table: @@ -267,6 +268,41 @@ def test_display_document_sentence(self): self.assertEqual(new_text, text1) +class TestDocumentsModel(WidgetTest): + def setUp(self): + self.model = model = DocumentsModel() + model[:] = [[1, 1, "Foo"], [1, np.nan, "Bar"], [1, 2, "Baz"]] + + self.view = view = SemanticListView() + view.setModel(model) + + def test_sort_nans(self): + model = self.model + + self.view.sortByColumn(1, Qt.DescendingOrder) + self.assertEqual(model.data(model.index(0, 2)), "Baz") + self.assertEqual(model.data(model.index(1, 2)), "Foo") + self.assertEqual(model.data(model.index(2, 2)), "Bar") + + self.view.sortByColumn(1, Qt.AscendingOrder) + self.assertEqual(model.data(model.index(0, 2)), "Foo") + self.assertEqual(model.data(model.index(1, 2)), "Baz") + self.assertEqual(model.data(model.index(2, 2)), "Bar") + + def test_sort_test_column(self): + model = self.model + + self.view.sortByColumn(2, Qt.DescendingOrder) + self.assertEqual(model.data(model.index(0, 2)), "Foo") + self.assertEqual(model.data(model.index(1, 2)), "Baz") + self.assertEqual(model.data(model.index(2, 2)), "Bar") + + self.view.sortByColumn(2, Qt.AscendingOrder) + self.assertEqual(model.data(model.index(0, 2)), "Bar") + self.assertEqual(model.data(model.index(1, 2)), "Baz") + self.assertEqual(model.data(model.index(2, 2)), "Foo") + + class DummySearch(SemanticSearch): def __call__(self, *args, **kwargs): return [