source: orange/Orange/OrangeWidgets/Prototypes/OWDataSort.py @ 11735:706aae49157a

Revision 11735:706aae49157a, 7.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 6 months ago (diff)

Sorting no longer requires special handling of unknown values.

RevLine 
[11721]1from operator import itemgetter
2
3from PyQt4.QtGui import (
4    QListView, QStyledItemDelegate, QApplication, QComboBox
5)
6
7from PyQt4.QtCore import Qt
8
9import Orange
10
11from OWItemModels import VariableListModel
12from OWWidget import OWWidget
13from OWContexts import DomainContextHandler
14import OWGUI
15
16
17NAME = "Data Sort"
18DESCRIPTION = "Sort instances in a data table"
19ICON = "icons/DataSort.svg"
20
21
22INPUTS = [("Data", Orange.data.Table, "setData")]
23OUTPUTS = [("Data", Orange.data.Table)]
24
25
26SortOrderRole = next(OWGUI.OrangeUserRole)
27
28
29def toInt(variant):
30    if type(variant).__name__ == "QVariant":
31        value, ok = variant.toInt()
32        if ok:
33            return value
34        else:
35            raise TypeError()
36    else:
37        return int(variant)
38
39
40def toSortOrder(variant):
41    try:
42        order = toInt(variant)
43    except TypeError:
44        order = Qt.AscendingOrder
45    return order
46
47
48class SortParamDelegate(QStyledItemDelegate):
49
50    def initStyleOption(self, option, index):
51        QStyledItemDelegate.initStyleOption(self, option, index)
52
53        order = toSortOrder(index.data(SortOrderRole))
54
55        if order == Qt.AscendingOrder:
56            option.text = option.text + " (Ascending)"
57        else:
58            option.text = option.text + " (Descending)"
59
60    def createEditor(self, parent, option, index):
61        editor = QComboBox(parent)
62        # Note: Qt.AscendingOrder == 0 and Qt.DescendingOrder == 1
63        editor.addItems(["Ascending", "Descending"])
64        editor.setFocusPolicy(Qt.StrongFocus)
65        return editor
66
67    def setEditorData(self, editor, index):
68        order = toSortOrder(index.data(SortOrderRole))
69        editor.setCurrentIndex(order)
70        editor.activated.connect(lambda i: self._commit(editor))
71
72    def setModelData(self, editor, model, index):
73        model.setData(index, editor.currentIndex(), SortOrderRole)
74
75    def _commit(self, editor):
76        # Notify the view the editor finished.
77        self.commitData.emit(editor)
78
79
80class OWDataSort(OWWidget):
81    contextHandlers = {
82        "": DomainContextHandler(
83            "", ["sortroles"]
84        )
85    }
86    settingsList = ["autoCommit"]
87
88    def __init__(self, parent=None, signalManger=None, title="Data Sort"):
89        super(OWDataSort, self).__init__(parent, signalManger, title,
90                                         wantMainArea=False)
91
92        #: Mapping (feature.name, feature.var_type) to (sort_index, sort_order)
93        #: where sirt index is the position of the feature in the sortByModel
94        #: and sort_order the Qt.SortOrder flag
95        self.sortroles = {}
96
97        self.autoCommit = False
98        self._outputChanged = False
99
100        box = OWGUI.widgetBox(self.controlArea, "Sort By Features")
101        self.sortByView = QListView()
102        self.sortByView.setItemDelegate(SortParamDelegate(self))
103        self.sortByView.setSelectionMode(QListView.ExtendedSelection)
104        self.sortByView.setDragDropMode(QListView.DragDrop)
105        self.sortByView.setDefaultDropAction(Qt.MoveAction)
106        self.sortByView.viewport().setAcceptDrops(True)
107
108        self.sortByModel = VariableListModel(
109            flags=Qt.ItemIsEnabled | Qt.ItemIsSelectable |
110                  Qt.ItemIsDragEnabled | Qt.ItemIsEditable
111        )
112        self.sortByView.setModel(self.sortByModel)
113
114        box.layout().addWidget(self.sortByView)
115
116        box = OWGUI.widgetBox(self.controlArea, "Unused Features")
117        self.unusedView = QListView()
118        self.unusedView.setSelectionMode(QListView.ExtendedSelection)
119        self.unusedView.setDragDropMode(QListView.DragDrop)
120        self.unusedView.setDefaultDropAction(Qt.MoveAction)
121        self.unusedView.viewport().setAcceptDrops(True)
122
123        self.unusedModel = VariableListModel(
124            flags=Qt.ItemIsEnabled | Qt.ItemIsSelectable |
125                  Qt.ItemIsDragEnabled
126        )
127        self.unusedView.setModel(self.unusedModel)
128
129        box.layout().addWidget(self.unusedView)
130
131        box = OWGUI.widgetBox(self.controlArea, "Output")
132        cb = OWGUI.checkBox(box, self, "autoCommit", "Auto commit")
133        b = OWGUI.button(box, self, "Commit", callback=self.commit)
134        OWGUI.setStopper(self, b, cb, "_outputChanged", callback=self.commit)
135
136    def setData(self, data):
137        """
138        Set the input data.
139        """
140        self._storeRoles()
141
142        self.closeContext("")
143        self.data = data
144
145        if data is not None:
146            self.openContext("", data)
147            domain = data.domain
148            features = (domain.variables + domain.class_vars +
149                        domain.get_metas().values())
150            sort_by = []
151            unused = []
152
153            for feat in features:
154                hint = self.sortroles.get((feat.name, feat.var_type), None)
155                if hint is not None:
156                    index, order = hint
157                    sort_by.append((feat, index, order))
158                else:
159                    unused.append(feat)
160
161            sort_by = sorted(sort_by, key=itemgetter(1))
162            self.sortByModel[:] = [feat for feat, _, _ in sort_by]
163            self.unusedModel[:] = unused
164
165            # Restore the sort orders
166            for i, (_, _, order) in enumerate(sort_by):
167                index = self.sortByModel.index(i, 0)
168                self.sortByModel.setData(index, order, SortOrderRole)
169
170        self.commit()
171
172    def _invalidate(self):
173        if self.autoCommit:
174            self.commit()
175        else:
176            self._outputChanged = True
177
178    def _sortingParams(self):
179        params = []
180
181        for i, feature in enumerate(self.sortByModel):
182            index = self.sortByModel.index(i, 0)
183            order = toSortOrder(index.data(SortOrderRole))
184            params.append((feature, order))
185
186        return params
187
188    def commit(self):
189        params = self._sortingParams()
190
191        if self.data:
192            instances = sorted(self.data, key=sort_key(params))
193
194            data = Orange.data.Table(self.data.domain, instances)
195        else:
196            data = self.data
197
198        self.send("Data", data)
199
200    def _storeRoles(self):
201        """
202        Store the sorting roles back into the stored settings.
203        """
204        roles = {}
205        for i, feature in enumerate(self.sortByModel):
206            index = self.sortByModel.index(i, 0)
207            order = toSortOrder(index.data(SortOrderRole))
208            roles[(feature.name, feature.var_type)] = (i, int(order))
209
210        self.sortroles = roles
211
212    def getSettings(self, *args, **kwargs):
213        self._storeRoles()
214        return OWWidget.getSettings(self, *args, **kwargs)
215
216
217def sort_key(params):
218    def key(inst):
219        return tuple(
[11735]220            inst[feature] if order == Qt.AscendingOrder
221                else rev_compare(inst[feature])
[11721]222            for feature, order in params
223        )
224    return key
225
226
227class rev_compare(object):
228    def __init__(self, obj):
229        self.obj = obj
230
231    def __eq__(self, other):
232        return self.obj == other.obj
233
234    def __lt__(self, other):
235        return not self.obj < other.obj
236
237
238if __name__ == "__main__":
239    app = QApplication([])
240    w = OWDataSort()
241    data = Orange.data.Table("iris")
242    w.setData(data)
243    w.show()
244    w.raise_()
245    app.exec_()
246    w.saveSettings()
Note: See TracBrowser for help on using the repository browser.