Skip to content

Commit

Permalink
Move TableView and TableModel outside of Rank
Browse files Browse the repository at this point in the history
  • Loading branch information
ajdapretnar committed Aug 22, 2022
1 parent 9d73b8e commit d6dc149
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 79 deletions.
79 changes: 7 additions & 72 deletions Orange/widgets/data/owrank.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
import warnings
from collections import namedtuple
from functools import partial
from itertools import chain
Expand All @@ -15,6 +14,8 @@
QButtonGroup, QCheckBox, QGridLayout, QHeaderView, QItemDelegate,
QRadioButton, QStackedWidget, QTableView
)

from Orange.widgets.gui import TableView, BarRatioTableModel
from orangewidget.settings import IncompatibleContext
from scipy.sparse import issparse

Expand All @@ -29,7 +30,6 @@
)
from Orange.widgets.unsupervised.owdistances import InterruptException
from Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState
from Orange.widgets.utils.itemmodels import PyTableModel
from Orange.widgets.utils.sql import check_sql_input
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import AttributeList, Input, MultiInput, Output, Msg, OWWidget
Expand Down Expand Up @@ -106,72 +106,6 @@ def mousePressEvent(self, event):
self.manualSelection.emit()


class TableModel(PyTableModel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._extremes = {}

def data(self, index, role=Qt.DisplayRole):
if role == gui.BarRatioRole and index.isValid():
value = super().data(index, Qt.EditRole)
if not isinstance(value, float):
return None
vmin, vmax = self._extremes.get(index.column(), (-np.inf, np.inf))
value = (value - vmin) / ((vmax - vmin) or 1)
return value

if role == Qt.DisplayRole and index.column() != VARNAME_COL:
role = Qt.EditRole

value = super().data(index, role)

# Display nothing for non-existent attr value counts in column 1
if role == Qt.EditRole \
and index.column() == NVAL_COL and np.isnan(value):
return ''

return value

def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.InitialSortOrderRole:
return Qt.DescendingOrder if section > 0 else Qt.AscendingOrder
return super().headerData(section, orientation, role)

def setExtremesFrom(self, column, values):
"""Set extremes for columnn's ratio bars from values"""
try:
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", ".*All-NaN slice encountered.*", RuntimeWarning)
vmin = np.nanmin(values)
if np.isnan(vmin):
raise TypeError
except TypeError:
vmin, vmax = -np.inf, np.inf
else:
vmax = np.nanmax(values)
self._extremes[column] = (vmin, vmax)

def resetSorting(self, yes_reset=False):
# pylint: disable=arguments-differ
"""We don't want to invalidate our sort proxy model everytime we
wrap a new list. Our proxymodel only invalidates explicitly
(i.e. when new data is set)"""
if yes_reset:
super().resetSorting()

def _argsortData(self, data, order):
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:
# Always sort NaNs last
return np.roll(indices, -np.isnan(data).sum())
return indices


class Results(SimpleNamespace):
method_scores: Tuple[ScoreMeta, np.ndarray] = None
scorer_scores: Tuple[ScoreMeta, Tuple[np.ndarray, List[str]]] = None
Expand Down Expand Up @@ -305,11 +239,12 @@ def __init__(self):
if method.is_default}

# GUI
self.ranksModel = model = TableModel(parent=self) # type: TableModel
self.ranksModel = model = BarRatioTableModel(parent=self) # type:
# BarRatioTableModel
self.ranksView = view = TableView(self) # type: TableView
self.mainArea.layout().addWidget(view)
view.setModel(model)
view.setColumnWidth(NVAL_COL, 30)
view.setColumnWidth(1, 30)
view.selectionModel().selectionChanged.connect(self.on_select)

def _set_select_manual():
Expand Down Expand Up @@ -528,8 +463,8 @@ def on_done(self, result: Results) -> None:

self.ranksModel.wrap(model_array.tolist())
self.ranksModel.setHorizontalHeaderLabels(('', '#',) + labels)
self.ranksView.setColumnWidth(NVAL_COL, 40)
self.ranksView.resizeColumnToContents(VARNAME_COL)
self.ranksView.setColumnWidth(1, 40)
self.ranksView.resizeColumnToContents(0)

# Re-apply sort
try:
Expand Down
4 changes: 2 additions & 2 deletions Orange/widgets/data/tests/test_owrank.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from Orange.regression import LinearRegressionLearner
from Orange.projection import PCA
from Orange.widgets.data.owrank import OWRank, ProblemType, CLS_SCORES, \
REG_SCORES, TableModel
REG_SCORES, BarRatioTableModel
from Orange.widgets.tests.base import WidgetTest, datasets
from Orange.widgets.widget import AttributeList

Expand Down Expand Up @@ -556,7 +556,7 @@ def test_concurrent_cancel(self):
class TestRankModel(GuiTest):
@staticmethod
def test_argsort():
func = TableModel()._argsortData # pylint: disable=protected-access
func = BarRatioTableModel()._argsortData # pylint: disable=protected-access
assert_equal = np.testing.assert_equal

test_array = np.array([4.2, 7.2, np.nan, 1.3, np.nan])
Expand Down
81 changes: 76 additions & 5 deletions Orange/widgets/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Wrappers for controls used in widgets
"""
import math
import numpy as np

import logging
import sys
Expand All @@ -10,9 +11,10 @@
from collections.abc import Sequence

from AnyQt import QtWidgets, QtCore, QtGui
from AnyQt.QtCore import Qt, QSize, QItemSelection
from AnyQt.QtCore import Qt, QSize, QItemSelection, pyqtSignal as Signal
from AnyQt.QtGui import QColor, QWheelEvent
from AnyQt.QtWidgets import QWidget, QListView, QComboBox
from AnyQt.QtWidgets import QWidget, QListView, QComboBox, QTableView, \
QItemDelegate

from orangewidget.utils.itemdelegates import (
BarItemDataDelegate as _BarItemDataDelegate
Expand Down Expand Up @@ -40,7 +42,7 @@
ControlledCallback, ControlledCallFront, ValueCallback, connectControl,
is_macstyle
)

from orangewidget.utils.itemmodels import PyTableModel

try:
# Some Orange widgets might expect this here
Expand All @@ -50,11 +52,10 @@
pass # Neither WebKit nor WebEngine are available

import Orange.data
from Orange.widgets.utils import getdeepattr
from Orange.widgets.utils import getdeepattr, vartype
from Orange.data import \
ContinuousVariable, StringVariable, TimeVariable, DiscreteVariable, \
Variable, Value
from Orange.widgets.utils import vartype

__all__ = [
# Re-exported
Expand Down Expand Up @@ -663,3 +664,73 @@ def wheelEvent(self, event: QWheelEvent):
super().wheelEvent(new_event)
else:
super().wheelEvent(event)


class BarRatioTableModel(PyTableModel):
"""A model for displaying python tables.
Adds a BarRatioRole that returns Data, normalized between the extremes.
NaNs are listed last when sorting."""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._extremes = {}

def data(self, index, role=Qt.DisplayRole):
if role == BarRatioRole and index.isValid():
value = super().data(index, Qt.EditRole)
if not isinstance(value, float):
return None
vmin, vmax = self._extremes.get(index.column(), (-np.inf, np.inf))
value = (value - vmin) / ((vmax - vmin) or 1)
return value

if role == Qt.DisplayRole and index.column() != 0:
role = Qt.EditRole

value = super().data(index, role)

# Display nothing for non-existent attr value counts in column 1
if role == Qt.EditRole \
and index.column() == 1 and np.isnan(value):
return ''

return value

def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.InitialSortOrderRole:
return Qt.DescendingOrder if section > 0 else Qt.AscendingOrder
return super().headerData(section, orientation, role)

def setExtremesFrom(self, column, values):
"""Set extremes for column's ratio bars from values"""
try:
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", ".*All-NaN slice encountered.*", RuntimeWarning)
vmin = np.nanmin(values)
if np.isnan(vmin):
raise TypeError
except TypeError:
vmin, vmax = -np.inf, np.inf
else:
vmax = np.nanmax(values)
self._extremes[column] = (vmin, vmax)

def resetSorting(self, yes_reset=False):
# pylint: disable=arguments-differ
"""We don't want to invalidate our sort proxy model everytime we
wrap a new list. Our proxymodel only invalidates explicitly
(i.e. when new data is set)"""
if yes_reset:
super().resetSorting()

def _argsortData(self, data, order):
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:
# Always sort NaNs last
return np.roll(indices, -np.isnan(data).sum())
return indices

0 comments on commit d6dc149

Please sign in to comment.